From 92ef925a22634926f99de80368937aade9a5720c Mon Sep 17 00:00:00 2001 From: dylanwrightCO <135221918+dylanwrightCO@users.noreply.github.com> Date: Mon, 17 Jul 2023 16:28:45 +0100 Subject: [PATCH] Feature/gap 1850 spadmin (#52) feat (super admin dashboard): adds functionality for super admin dashboard --------- Co-authored-by: john-tco Co-authored-by: Dylan Wright Co-authored-by: Dominic West --- pom.xml | 56 +++++- .../dto/ChangeDepartmentPageDto.java | 14 ++ .../gapuserservice/dto/DepartmentDto.java | 12 ++ .../dto/OneLoginUserInfoDto.java | 2 +- .../gapuserservice/dto/RoleDto.java | 12 ++ .../dto/SuperAdminDashboardPageDto.java | 15 ++ .../gapuserservice/dto/UserDto.java | 17 ++ .../DepartmentNotFoundException.java | 11 ++ .../exceptions/UserNotFoundException.java | 4 + .../mappers/DepartmentMapper.java | 10 ++ .../gapuserservice/mappers/RoleMapper.java | 10 ++ .../gapuserservice/mappers/UserMapper.java | 10 ++ .../gapuserservice/model/Department.java | 4 +- .../gapuserservice/model/Role.java | 6 + .../gapuserservice/model/User.java | 21 ++- .../repository/DepartmentRepository.java | 9 + .../repository/RoleRepository.java | 1 + .../repository/UserRepository.java | 9 +- .../security/JwtTokenFilter.java | 4 +- .../security/WebSecurityConfig.java | 3 +- .../service/DepartmentService.java | 23 +++ .../service/OneLoginService.java | 10 +- .../gapuserservice/service/RoleService.java | 22 +++ .../service/jwt/impl/ColaJwtServiceImpl.java | 1 + .../service/user/OneLoginUserService.java | 74 ++++++++ .../gapuserservice/web/LoginControllerV2.java | 12 +- .../gapuserservice/web/RoleController.java | 21 +++ .../web/SuperAdminController.java | 42 +++++ .../gapuserservice/web/UserController.java | 58 +++++++ .../db/migration/V1_4__update_user_tables.sql | 4 +- .../db/migration/V1_5__update_role_table.sql | 2 + .../gapuserservice/model/UserTest.java | 2 +- .../service/DepartmentServiceTest.java | 47 +++++ .../service/OneLoginServiceTest.java | 10 +- .../service/RoleServiceTest.java | 45 +++++ .../service/user/OneLoginUserServiceTest.java | 163 ++++++++++++++++++ .../web/LoginControllerV2Test.java | 12 +- .../web/RoleControllerTest.java | 33 ++++ .../web/SuperAdminControllerTest.java | 65 +++++++ .../web/UserControllerTest.java | 68 ++++++++ 40 files changed, 903 insertions(+), 41 deletions(-) create mode 100644 src/main/java/gov/cabinetofice/gapuserservice/dto/ChangeDepartmentPageDto.java create mode 100644 src/main/java/gov/cabinetofice/gapuserservice/dto/DepartmentDto.java create mode 100644 src/main/java/gov/cabinetofice/gapuserservice/dto/RoleDto.java create mode 100644 src/main/java/gov/cabinetofice/gapuserservice/dto/SuperAdminDashboardPageDto.java create mode 100644 src/main/java/gov/cabinetofice/gapuserservice/dto/UserDto.java create mode 100644 src/main/java/gov/cabinetofice/gapuserservice/exceptions/DepartmentNotFoundException.java create mode 100644 src/main/java/gov/cabinetofice/gapuserservice/mappers/DepartmentMapper.java create mode 100644 src/main/java/gov/cabinetofice/gapuserservice/mappers/RoleMapper.java create mode 100644 src/main/java/gov/cabinetofice/gapuserservice/mappers/UserMapper.java create mode 100644 src/main/java/gov/cabinetofice/gapuserservice/repository/DepartmentRepository.java create mode 100644 src/main/java/gov/cabinetofice/gapuserservice/service/DepartmentService.java create mode 100644 src/main/java/gov/cabinetofice/gapuserservice/service/RoleService.java create mode 100644 src/main/java/gov/cabinetofice/gapuserservice/service/user/OneLoginUserService.java create mode 100644 src/main/java/gov/cabinetofice/gapuserservice/web/RoleController.java create mode 100644 src/main/java/gov/cabinetofice/gapuserservice/web/SuperAdminController.java create mode 100644 src/main/java/gov/cabinetofice/gapuserservice/web/UserController.java create mode 100644 src/main/resources/db/migration/V1_5__update_role_table.sql create mode 100644 src/test/java/gov/cabinetofice/gapuserservice/service/DepartmentServiceTest.java create mode 100644 src/test/java/gov/cabinetofice/gapuserservice/service/RoleServiceTest.java create mode 100644 src/test/java/gov/cabinetofice/gapuserservice/service/user/OneLoginUserServiceTest.java create mode 100644 src/test/java/gov/cabinetofice/gapuserservice/web/RoleControllerTest.java create mode 100644 src/test/java/gov/cabinetofice/gapuserservice/web/SuperAdminControllerTest.java create mode 100644 src/test/java/gov/cabinetofice/gapuserservice/web/UserControllerTest.java diff --git a/pom.xml b/pom.xml index f2583d74..e22c8892 100644 --- a/pom.xml +++ b/pom.xml @@ -1,6 +1,6 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 org.springframework.boot @@ -39,6 +39,19 @@ org.springframework.boot spring-boot-starter-security + + + org.mapstruct + mapstruct + 1.5.2.Final + + + org.mapstruct + mapstruct-processor + 1.5.2.Final + + + org.springframework.security spring-security-core @@ -181,9 +194,9 @@ org.flywaydb flyway-maven-plugin - jdbc:postgresql://localhost:5433/gapuserlocaldb - root - root + jdbc:postgresql://localhost:5432/gapuserlocaldb + postgres + postgres false @@ -197,6 +210,39 @@ + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 17 + 17 + + + org.mapstruct + mapstruct-processor + 1.5.1.Final + + + org.projectlombok + lombok + 1.18.24 + + + org.projectlombok + lombok-mapstruct-binding + 0.2.0 + + + + + + + + + + + org.apache.maven.plugins maven-antrun-plugin @@ -245,7 +291,7 @@ - + \ No newline at end of file diff --git a/src/main/java/gov/cabinetofice/gapuserservice/dto/ChangeDepartmentPageDto.java b/src/main/java/gov/cabinetofice/gapuserservice/dto/ChangeDepartmentPageDto.java new file mode 100644 index 00000000..20bb99e9 --- /dev/null +++ b/src/main/java/gov/cabinetofice/gapuserservice/dto/ChangeDepartmentPageDto.java @@ -0,0 +1,14 @@ +package gov.cabinetofice.gapuserservice.dto; + +import gov.cabinetofice.gapuserservice.model.User; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +public class ChangeDepartmentPageDto { + private User user; + private List departments; +} diff --git a/src/main/java/gov/cabinetofice/gapuserservice/dto/DepartmentDto.java b/src/main/java/gov/cabinetofice/gapuserservice/dto/DepartmentDto.java new file mode 100644 index 00000000..7c1f0829 --- /dev/null +++ b/src/main/java/gov/cabinetofice/gapuserservice/dto/DepartmentDto.java @@ -0,0 +1,12 @@ +package gov.cabinetofice.gapuserservice.dto; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class DepartmentDto { + private String id; + private String name; + private String ggisID; +} \ No newline at end of file diff --git a/src/main/java/gov/cabinetofice/gapuserservice/dto/OneLoginUserInfoDto.java b/src/main/java/gov/cabinetofice/gapuserservice/dto/OneLoginUserInfoDto.java index be722547..024541fd 100644 --- a/src/main/java/gov/cabinetofice/gapuserservice/dto/OneLoginUserInfoDto.java +++ b/src/main/java/gov/cabinetofice/gapuserservice/dto/OneLoginUserInfoDto.java @@ -6,6 +6,6 @@ @Data @Builder public class OneLoginUserInfoDto { - private String email; + private String emailAddress; private String sub; } diff --git a/src/main/java/gov/cabinetofice/gapuserservice/dto/RoleDto.java b/src/main/java/gov/cabinetofice/gapuserservice/dto/RoleDto.java new file mode 100644 index 00000000..f67006db --- /dev/null +++ b/src/main/java/gov/cabinetofice/gapuserservice/dto/RoleDto.java @@ -0,0 +1,12 @@ +package gov.cabinetofice.gapuserservice.dto; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class RoleDto { + private String id; + private String name; + private String description; +} \ No newline at end of file diff --git a/src/main/java/gov/cabinetofice/gapuserservice/dto/SuperAdminDashboardPageDto.java b/src/main/java/gov/cabinetofice/gapuserservice/dto/SuperAdminDashboardPageDto.java new file mode 100644 index 00000000..55f752fc --- /dev/null +++ b/src/main/java/gov/cabinetofice/gapuserservice/dto/SuperAdminDashboardPageDto.java @@ -0,0 +1,15 @@ +package gov.cabinetofice.gapuserservice.dto; + +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +public class SuperAdminDashboardPageDto { + private List roles; + private List departments; + private List users; + private long userCount; +} diff --git a/src/main/java/gov/cabinetofice/gapuserservice/dto/UserDto.java b/src/main/java/gov/cabinetofice/gapuserservice/dto/UserDto.java new file mode 100644 index 00000000..6de75f56 --- /dev/null +++ b/src/main/java/gov/cabinetofice/gapuserservice/dto/UserDto.java @@ -0,0 +1,17 @@ +package gov.cabinetofice.gapuserservice.dto; + +import gov.cabinetofice.gapuserservice.model.Department; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Builder +@Data +public class UserDto { + private String gapUserId; + private String emailAddress; + private String sub; + private List roles; + private Department department; +} diff --git a/src/main/java/gov/cabinetofice/gapuserservice/exceptions/DepartmentNotFoundException.java b/src/main/java/gov/cabinetofice/gapuserservice/exceptions/DepartmentNotFoundException.java new file mode 100644 index 00000000..a83172da --- /dev/null +++ b/src/main/java/gov/cabinetofice/gapuserservice/exceptions/DepartmentNotFoundException.java @@ -0,0 +1,11 @@ +package gov.cabinetofice.gapuserservice.exceptions; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.NOT_FOUND) +public class DepartmentNotFoundException extends RuntimeException { + public DepartmentNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/gov/cabinetofice/gapuserservice/exceptions/UserNotFoundException.java b/src/main/java/gov/cabinetofice/gapuserservice/exceptions/UserNotFoundException.java index 88521186..56628f3a 100644 --- a/src/main/java/gov/cabinetofice/gapuserservice/exceptions/UserNotFoundException.java +++ b/src/main/java/gov/cabinetofice/gapuserservice/exceptions/UserNotFoundException.java @@ -1,5 +1,9 @@ package gov.cabinetofice.gapuserservice.exceptions; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.NOT_FOUND) public class UserNotFoundException extends RuntimeException { public UserNotFoundException(String message) { super(message); diff --git a/src/main/java/gov/cabinetofice/gapuserservice/mappers/DepartmentMapper.java b/src/main/java/gov/cabinetofice/gapuserservice/mappers/DepartmentMapper.java new file mode 100644 index 00000000..c78dbdd5 --- /dev/null +++ b/src/main/java/gov/cabinetofice/gapuserservice/mappers/DepartmentMapper.java @@ -0,0 +1,10 @@ +package gov.cabinetofice.gapuserservice.mappers; + +import gov.cabinetofice.gapuserservice.dto.DepartmentDto; +import gov.cabinetofice.gapuserservice.model.Department; +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring") +public interface DepartmentMapper { + DepartmentDto departmentToDepartmentDto(Department dept); +} diff --git a/src/main/java/gov/cabinetofice/gapuserservice/mappers/RoleMapper.java b/src/main/java/gov/cabinetofice/gapuserservice/mappers/RoleMapper.java new file mode 100644 index 00000000..fcb515f4 --- /dev/null +++ b/src/main/java/gov/cabinetofice/gapuserservice/mappers/RoleMapper.java @@ -0,0 +1,10 @@ +package gov.cabinetofice.gapuserservice.mappers; +import gov.cabinetofice.gapuserservice.model.Role; + +import gov.cabinetofice.gapuserservice.dto.RoleDto; +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring") +public interface RoleMapper { + RoleDto roleToRoleDto(Role role); +} diff --git a/src/main/java/gov/cabinetofice/gapuserservice/mappers/UserMapper.java b/src/main/java/gov/cabinetofice/gapuserservice/mappers/UserMapper.java new file mode 100644 index 00000000..4f0b66a2 --- /dev/null +++ b/src/main/java/gov/cabinetofice/gapuserservice/mappers/UserMapper.java @@ -0,0 +1,10 @@ +package gov.cabinetofice.gapuserservice.mappers; + +import gov.cabinetofice.gapuserservice.dto.UserDto; +import gov.cabinetofice.gapuserservice.model.User; +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring") +public interface UserMapper { + UserDto userToUserDto(User user); +} diff --git a/src/main/java/gov/cabinetofice/gapuserservice/model/Department.java b/src/main/java/gov/cabinetofice/gapuserservice/model/Department.java index 68893441..91b8d0e8 100644 --- a/src/main/java/gov/cabinetofice/gapuserservice/model/Department.java +++ b/src/main/java/gov/cabinetofice/gapuserservice/model/Department.java @@ -1,5 +1,6 @@ package gov.cabinetofice.gapuserservice.model; +import com.fasterxml.jackson.annotation.JsonBackReference; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import jakarta.persistence.*; import lombok.*; @@ -29,9 +30,10 @@ public class Department { private String ggisID; @Column(name = "users") - @OneToMany(mappedBy="id", cascade = { CascadeType.MERGE, CascadeType.PERSIST }, fetch = FetchType.LAZY) + @OneToMany(mappedBy="gapUserId", cascade = { CascadeType.MERGE, CascadeType.PERSIST }, fetch = FetchType.LAZY) @ToString.Exclude @JsonIgnoreProperties({ "hibernateLazyInitializer" }) + @JsonBackReference @Builder.Default private List users = new ArrayList<>(); } diff --git a/src/main/java/gov/cabinetofice/gapuserservice/model/Role.java b/src/main/java/gov/cabinetofice/gapuserservice/model/Role.java index 00ac36e6..a3605dac 100644 --- a/src/main/java/gov/cabinetofice/gapuserservice/model/Role.java +++ b/src/main/java/gov/cabinetofice/gapuserservice/model/Role.java @@ -1,5 +1,6 @@ package gov.cabinetofice.gapuserservice.model; +import com.fasterxml.jackson.annotation.JsonBackReference; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import jakarta.persistence.*; import lombok.*; @@ -26,15 +27,20 @@ public class Role { @Enumerated(EnumType.STRING) private RoleEnum name; + @Column(name = "description") + private String description; + @ManyToMany(cascade = { CascadeType.MERGE, CascadeType.PERSIST }, fetch = FetchType.LAZY) @JoinColumn(name = "id", nullable = false) @ToString.Exclude @JsonIgnoreProperties({ "hibernateLazyInitializer" }) + @JsonBackReference @Builder.Default private List users = new ArrayList<>(); public void addUser(User user) { this.users.add(user); } + public void removeUser( User user) { this.users.remove(user); } } diff --git a/src/main/java/gov/cabinetofice/gapuserservice/model/User.java b/src/main/java/gov/cabinetofice/gapuserservice/model/User.java index d7f71f4e..d31e269b 100644 --- a/src/main/java/gov/cabinetofice/gapuserservice/model/User.java +++ b/src/main/java/gov/cabinetofice/gapuserservice/model/User.java @@ -1,6 +1,7 @@ package gov.cabinetofice.gapuserservice.model; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonManagedReference; import jakarta.persistence.*; import lombok.*; @@ -20,23 +21,25 @@ public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "gap_user_id") - private Integer id; + private Integer gapUserId; @Column(name = "email") - private String email; + private String emailAddress; @Column(name = "sub") private String sub; - @ManyToMany(cascade = { CascadeType.MERGE, CascadeType.PERSIST }, fetch = FetchType.EAGER, mappedBy = "users") + @ManyToMany(cascade = { CascadeType.MERGE, CascadeType.PERSIST }, fetch = FetchType.LAZY, mappedBy = "users") @ToString.Exclude @JsonIgnoreProperties({ "hibernateLazyInitializer" }) + @JsonManagedReference @Builder.Default private List roles = new ArrayList<>(); - @ManyToOne(cascade = { CascadeType.MERGE, CascadeType.PERSIST }, fetch = FetchType.EAGER) + @ManyToOne(cascade = { CascadeType.MERGE, CascadeType.PERSIST }, fetch = FetchType.LAZY) @JoinColumn(name = "dept_id") @ToString.Exclude + @JsonManagedReference @JsonIgnoreProperties({ "hibernateLazyInitializer" }) private Department department; @@ -50,7 +53,7 @@ public boolean hasSub() { } public boolean hasEmail() { - return this.email != null; + return this.emailAddress != null; } public boolean hasDepartment() { @@ -68,4 +71,12 @@ public boolean isAdmin() { public boolean isSuperAdmin() { return this.roles.stream().anyMatch((role) -> role.getName().equals(RoleEnum.SUPER_ADMIN)); } + + public User removeAllRoles() { + for (Role role : this.roles) { + role.removeUser(this); + } + this.roles.clear(); + return this; + } } diff --git a/src/main/java/gov/cabinetofice/gapuserservice/repository/DepartmentRepository.java b/src/main/java/gov/cabinetofice/gapuserservice/repository/DepartmentRepository.java new file mode 100644 index 00000000..3653b5f4 --- /dev/null +++ b/src/main/java/gov/cabinetofice/gapuserservice/repository/DepartmentRepository.java @@ -0,0 +1,9 @@ +package gov.cabinetofice.gapuserservice.repository; + +import gov.cabinetofice.gapuserservice.model.Department; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface DepartmentRepository extends JpaRepository { +} diff --git a/src/main/java/gov/cabinetofice/gapuserservice/repository/RoleRepository.java b/src/main/java/gov/cabinetofice/gapuserservice/repository/RoleRepository.java index 8c8ad13a..5ac6e72f 100644 --- a/src/main/java/gov/cabinetofice/gapuserservice/repository/RoleRepository.java +++ b/src/main/java/gov/cabinetofice/gapuserservice/repository/RoleRepository.java @@ -10,5 +10,6 @@ @Repository public interface RoleRepository extends JpaRepository { Optional findByName(RoleEnum name); + } diff --git a/src/main/java/gov/cabinetofice/gapuserservice/repository/UserRepository.java b/src/main/java/gov/cabinetofice/gapuserservice/repository/UserRepository.java index 1c10f8bf..f46aeaff 100644 --- a/src/main/java/gov/cabinetofice/gapuserservice/repository/UserRepository.java +++ b/src/main/java/gov/cabinetofice/gapuserservice/repository/UserRepository.java @@ -1,6 +1,7 @@ package gov.cabinetofice.gapuserservice.repository; import gov.cabinetofice.gapuserservice.model.User; +import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -8,7 +9,13 @@ @Repository public interface UserRepository extends JpaRepository { - Optional findByEmail(String email); + + @EntityGraph(attributePaths = {"department", "roles"}) + Optional findByEmailAddress(String email); + @EntityGraph(attributePaths = {"department", "roles"}) Optional findBySub(String sub); + + @EntityGraph(attributePaths = {"department", "roles"}) + Optional findById(int id); } diff --git a/src/main/java/gov/cabinetofice/gapuserservice/security/JwtTokenFilter.java b/src/main/java/gov/cabinetofice/gapuserservice/security/JwtTokenFilter.java index 4721290c..ca2c4eb5 100644 --- a/src/main/java/gov/cabinetofice/gapuserservice/security/JwtTokenFilter.java +++ b/src/main/java/gov/cabinetofice/gapuserservice/security/JwtTokenFilter.java @@ -65,12 +65,12 @@ protected void doFilterInternal(final @NonNull HttpServletRequest request, } //TODO set the Security context, so we can access user details in rest of app - final UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( + final UsernamePasswordAuthenticationToken user_authentication = new UsernamePasswordAuthenticationToken( "Placeholder", null, Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))); - SecurityContextHolder.getContext().setAuthentication(authentication); + SecurityContextHolder.getContext().setAuthentication(user_authentication); chain.doFilter(request, response); } diff --git a/src/main/java/gov/cabinetofice/gapuserservice/security/WebSecurityConfig.java b/src/main/java/gov/cabinetofice/gapuserservice/security/WebSecurityConfig.java index 4f470cc3..2897761a 100644 --- a/src/main/java/gov/cabinetofice/gapuserservice/security/WebSecurityConfig.java +++ b/src/main/java/gov/cabinetofice/gapuserservice/security/WebSecurityConfig.java @@ -50,8 +50,7 @@ public WebSecurityCustomizer webSecurityCustomizer() { "/is-user-logged-in", "/redirect-after-cola-login", "/error/**", - "/.well-known/jwks.json", - "/logout" + "/.well-known/jwks.json" ); } diff --git a/src/main/java/gov/cabinetofice/gapuserservice/service/DepartmentService.java b/src/main/java/gov/cabinetofice/gapuserservice/service/DepartmentService.java new file mode 100644 index 00000000..4edbeabb --- /dev/null +++ b/src/main/java/gov/cabinetofice/gapuserservice/service/DepartmentService.java @@ -0,0 +1,23 @@ +package gov.cabinetofice.gapuserservice.service; + + +import gov.cabinetofice.gapuserservice.dto.DepartmentDto; +import gov.cabinetofice.gapuserservice.mappers.DepartmentMapper; +import gov.cabinetofice.gapuserservice.repository.DepartmentRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class DepartmentService { + private final DepartmentRepository departmentRepository; + private final DepartmentMapper departmentMapper; + public List getAllDepartments() { + return departmentRepository.findAll().stream() + .map(departmentMapper::departmentToDepartmentDto) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/gov/cabinetofice/gapuserservice/service/OneLoginService.java b/src/main/java/gov/cabinetofice/gapuserservice/service/OneLoginService.java index ad553480..9119d05d 100644 --- a/src/main/java/gov/cabinetofice/gapuserservice/service/OneLoginService.java +++ b/src/main/java/gov/cabinetofice/gapuserservice/service/OneLoginService.java @@ -2,6 +2,7 @@ import gov.cabinetofice.gapuserservice.dto.OneLoginUserInfoDto; import gov.cabinetofice.gapuserservice.exceptions.*; +import gov.cabinetofice.gapuserservice.mappers.RoleMapper; import gov.cabinetofice.gapuserservice.model.Role; import gov.cabinetofice.gapuserservice.model.RoleEnum; import gov.cabinetofice.gapuserservice.model.User; @@ -47,6 +48,7 @@ public class OneLoginService { private final UserRepository userRepository; private final RoleRepository roleRepository; + private final RoleMapper roleMapper; public String createOneLoginJwt() { @@ -71,7 +73,7 @@ public OneLoginUserInfoDto getUserInfo(String accessToken) { final JSONObject userInfo = RestUtils.getRequestWithHeaders(oneLoginBaseUrl + "/userinfo", headers); return OneLoginUserInfoDto.builder() - .email(userInfo.getString("email")) + .emailAddress(userInfo.getString("email")) .sub(userInfo.getString("sub")) .build(); } catch (IOException e) { @@ -119,7 +121,7 @@ public List getNewUserRoles() { public User createUser(final String sub, final String email) { final User user = User.builder() .sub(sub) - .email(email) + .emailAddress(email) .build(); final List newUserRoles = getNewUserRoles(); for (RoleEnum roleEnum : newUserRoles) { @@ -131,7 +133,7 @@ public User createUser(final String sub, final String email) { } public void addSubToUser(final String sub, final String email) { - final User user = userRepository.findByEmail(email).orElseThrow(() -> new UserNotFoundException("Could not add sub to user: User with email '" + email + "' not found")); + final User user = userRepository.findByEmailAddress(email).orElseThrow(() -> new UserNotFoundException("Could not add sub to user: User with email '" + email + "' not found")); user.setSub(sub); userRepository.save(user); } @@ -139,6 +141,6 @@ public void addSubToUser(final String sub, final String email) { public Optional getUser(final String email, final String sub) { final Optional userBySub = userRepository.findBySub(sub); if (userBySub.isPresent()) return userBySub; - return userRepository.findByEmail(email); + return userRepository.findByEmailAddress(email); } } diff --git a/src/main/java/gov/cabinetofice/gapuserservice/service/RoleService.java b/src/main/java/gov/cabinetofice/gapuserservice/service/RoleService.java new file mode 100644 index 00000000..6de33ae4 --- /dev/null +++ b/src/main/java/gov/cabinetofice/gapuserservice/service/RoleService.java @@ -0,0 +1,22 @@ +package gov.cabinetofice.gapuserservice.service; + +import gov.cabinetofice.gapuserservice.dto.RoleDto; +import gov.cabinetofice.gapuserservice.mappers.RoleMapper; +import gov.cabinetofice.gapuserservice.repository.RoleRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class RoleService { + private final RoleRepository roleRepository; + private final RoleMapper roleMapper; + public List getAllRoles() { + return roleRepository.findAll().stream() + .map(roleMapper::roleToRoleDto) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/gov/cabinetofice/gapuserservice/service/jwt/impl/ColaJwtServiceImpl.java b/src/main/java/gov/cabinetofice/gapuserservice/service/jwt/impl/ColaJwtServiceImpl.java index e0d9cb42..a93a5a57 100644 --- a/src/main/java/gov/cabinetofice/gapuserservice/service/jwt/impl/ColaJwtServiceImpl.java +++ b/src/main/java/gov/cabinetofice/gapuserservice/service/jwt/impl/ColaJwtServiceImpl.java @@ -32,6 +32,7 @@ public class ColaJwtServiceImpl implements JwtService { @Override public boolean isTokenValid(final String colaJwt) { + final String trimmedToken = URLDecoder.decode(colaJwt, StandardCharsets.UTF_8).substring(2); if (!isValidColaSignature(trimmedToken)) { diff --git a/src/main/java/gov/cabinetofice/gapuserservice/service/user/OneLoginUserService.java b/src/main/java/gov/cabinetofice/gapuserservice/service/user/OneLoginUserService.java new file mode 100644 index 00000000..edfac579 --- /dev/null +++ b/src/main/java/gov/cabinetofice/gapuserservice/service/user/OneLoginUserService.java @@ -0,0 +1,74 @@ +package gov.cabinetofice.gapuserservice.service.user; + +import gov.cabinetofice.gapuserservice.dto.UserDto; +import gov.cabinetofice.gapuserservice.exceptions.DepartmentNotFoundException; +import gov.cabinetofice.gapuserservice.exceptions.UserNotFoundException; +import gov.cabinetofice.gapuserservice.mappers.UserMapper; +import gov.cabinetofice.gapuserservice.model.Department; +import gov.cabinetofice.gapuserservice.model.Role; +import gov.cabinetofice.gapuserservice.model.User; +import gov.cabinetofice.gapuserservice.repository.DepartmentRepository; +import gov.cabinetofice.gapuserservice.repository.RoleRepository; +import gov.cabinetofice.gapuserservice.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@RequiredArgsConstructor +@Service +public class OneLoginUserService { + + private final UserRepository userRepository; + private final DepartmentRepository departmentRepository; + private final UserMapper userMapper; + private final RoleRepository roleRepository; + + public List getPaginatedUsers(Pageable pageable) { + return userRepository.findAll(pageable).stream() + .map(userMapper::userToUserDto) + .collect(Collectors.toList()); + } + + public User getUserById(int id) { + return userRepository.findById(id) + .orElseThrow(() -> new UserNotFoundException("user with id: " + id + "not found")); + } + + public long getUserCount() { + return userRepository.count(); + } + + public User updateDepartment(Integer id, Integer departmentId) { + Optional optionalUser = userRepository.findById(id); + + if (optionalUser.isEmpty()) { + throw new UserNotFoundException("User not found"); + } + + Optional optionalDepartment = departmentRepository.findById(departmentId); + if (optionalDepartment.isEmpty()) { + throw new DepartmentNotFoundException("Department not found"); + } + + User user = optionalUser.get(); + Department department = optionalDepartment.get(); + + user.setDepartment(department); + return userRepository.save(user); + } + + public User updateRoles(Integer id, List newRoles) { + User user = userRepository.findById(id).orElseThrow(()-> new RuntimeException("User not found")); + user.removeAllRoles(); + for (Integer roleId : newRoles) { + Role role = roleRepository.findById(roleId).orElseThrow(); + user.addRole(role); + } + userRepository.save(user); + return user; + } +} \ No newline at end of file diff --git a/src/main/java/gov/cabinetofice/gapuserservice/web/LoginControllerV2.java b/src/main/java/gov/cabinetofice/gapuserservice/web/LoginControllerV2.java index 71c4b8f7..ff1ce50b 100644 --- a/src/main/java/gov/cabinetofice/gapuserservice/web/LoginControllerV2.java +++ b/src/main/java/gov/cabinetofice/gapuserservice/web/LoginControllerV2.java @@ -81,7 +81,7 @@ public RedirectView redirectAfterLogin(final @CookieValue(name = REDIRECT_URL_CO final String jwt = oneLoginService.createOneLoginJwt(); final String authToken = oneLoginService.getAuthToken(jwt, code); final OneLoginUserInfoDto userInfo = oneLoginService.getUserInfo(authToken); - final Optional userOptional = oneLoginService.getUser(userInfo.getEmail(), userInfo.getSub()); + final Optional userOptional = oneLoginService.getUser(userInfo.getEmailAddress(), userInfo.getSub()); final Cookie customJwt = generateCustomJwtCookie(userInfo, userOptional); response.addCookie(customJwt); @@ -94,18 +94,18 @@ public RedirectView redirectAfterLogin(final @CookieValue(name = REDIRECT_URL_CO return new RedirectView("/should-migrate-data"); } else { // TODO GAP-1932: Migrate cola user data to this admin - oneLoginService.addSubToUser(userInfo.getSub(), user.getEmail()); + oneLoginService.addSubToUser(userInfo.getSub(), user.getEmailAddress()); return getRedirectView(user, redirectUrl); } } - final User user = oneLoginService.createUser(userInfo.getSub(), userInfo.getEmail()); + final User user = oneLoginService.createUser(userInfo.getSub(), userInfo.getEmailAddress()); return getRedirectView(user, redirectUrl); } private Cookie generateCustomJwtCookie(final OneLoginUserInfoDto userInfo, final Optional userOptional) { final Map claims = new HashMap<>(); - claims.put("email", userInfo.getEmail()); + claims.put("email", userInfo.getEmailAddress()); claims.put("sub", userInfo.getSub()); if(userOptional.isPresent()) { @@ -128,8 +128,8 @@ private Cookie generateCustomJwtCookie(final OneLoginUserInfoDto userInfo, final private RedirectView getRedirectView(final User user, final Optional redirectUrl) { if(user.isSuperAdmin()) return new RedirectView(adminBaseUrl + "/super-admin/dashboard"); - if(user.isAdmin()) return new RedirectView(adminBaseUrl + "/dashboard"); + if(user.isAdmin()) return new RedirectView(adminBaseUrl); return new RedirectView((redirectUrl.orElse(configProperties.getDefaultRedirectUrl()))); } -} \ No newline at end of file +} diff --git a/src/main/java/gov/cabinetofice/gapuserservice/web/RoleController.java b/src/main/java/gov/cabinetofice/gapuserservice/web/RoleController.java new file mode 100644 index 00000000..3826a06b --- /dev/null +++ b/src/main/java/gov/cabinetofice/gapuserservice/web/RoleController.java @@ -0,0 +1,21 @@ +package gov.cabinetofice.gapuserservice.web; + +import gov.cabinetofice.gapuserservice.dto.RoleDto; +import gov.cabinetofice.gapuserservice.service.RoleService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RequiredArgsConstructor +@RestController +public class RoleController { + private final RoleService roleService; + + @GetMapping("/role") + public ResponseEntity> getAll() { + return ResponseEntity.ok(roleService.getAllRoles()); + } +} diff --git a/src/main/java/gov/cabinetofice/gapuserservice/web/SuperAdminController.java b/src/main/java/gov/cabinetofice/gapuserservice/web/SuperAdminController.java new file mode 100644 index 00000000..b34dd9ed --- /dev/null +++ b/src/main/java/gov/cabinetofice/gapuserservice/web/SuperAdminController.java @@ -0,0 +1,42 @@ +package gov.cabinetofice.gapuserservice.web; + +import gov.cabinetofice.gapuserservice.dto.DepartmentDto; +import gov.cabinetofice.gapuserservice.dto.RoleDto; +import gov.cabinetofice.gapuserservice.dto.SuperAdminDashboardPageDto; +import gov.cabinetofice.gapuserservice.dto.UserDto; +import gov.cabinetofice.gapuserservice.service.DepartmentService; +import gov.cabinetofice.gapuserservice.service.RoleService; +import gov.cabinetofice.gapuserservice.service.user.OneLoginUserService; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +import java.util.List; + + +@RequiredArgsConstructor +@Controller +@ConditionalOnProperty(value = "feature.onelogin.enabled", havingValue = "true") +public class SuperAdminController { + private final DepartmentService departmentService; + private final RoleService roleService; + private final OneLoginUserService oneLoginUserService; + + @GetMapping("/super-admin-dashboard") + public ResponseEntity superAdminDashboard(final Pageable pagination) { + List departments = departmentService.getAllDepartments(); + List roles = roleService.getAllRoles(); + List users = oneLoginUserService.getPaginatedUsers(pagination); + long userCount = oneLoginUserService.getUserCount(); + + return ResponseEntity.ok(SuperAdminDashboardPageDto.builder() + .departments(departments) + .roles(roles) + .users(users) + .userCount(userCount) + .build()); + } +} \ No newline at end of file diff --git a/src/main/java/gov/cabinetofice/gapuserservice/web/UserController.java b/src/main/java/gov/cabinetofice/gapuserservice/web/UserController.java new file mode 100644 index 00000000..a5f2c217 --- /dev/null +++ b/src/main/java/gov/cabinetofice/gapuserservice/web/UserController.java @@ -0,0 +1,58 @@ +package gov.cabinetofice.gapuserservice.web; + +import gov.cabinetofice.gapuserservice.dto.ChangeDepartmentPageDto; +import gov.cabinetofice.gapuserservice.dto.DepartmentDto; +import gov.cabinetofice.gapuserservice.model.User; +import gov.cabinetofice.gapuserservice.service.DepartmentService; +import gov.cabinetofice.gapuserservice.service.user.OneLoginUserService; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestBody; + +import java.util.List; + + +@RequiredArgsConstructor +@Controller +@ConditionalOnProperty(value = "feature.onelogin.enabled", havingValue = "true") +public class UserController { + + private final OneLoginUserService oneLoginUserService; + private final DepartmentService departmentService; + + @GetMapping("/user/{id}") + public ResponseEntity getUserById(@PathVariable("id") Integer id) { + return ResponseEntity.ok(oneLoginUserService.getUserById(id)); + } + + @PatchMapping("/user/{userId}/department") + public ResponseEntity updateDepartment(@PathVariable("userId") Integer userId, + @RequestParam(value = "departmentId", required = false) Integer departmentId) { + if(departmentId == null) return ResponseEntity.ok().build(); + User user = oneLoginUserService.updateDepartment(userId, departmentId); + return ResponseEntity.ok(user); + } + + @GetMapping("/page/user/{userId}/change-department") + public ResponseEntity getChangeDepartmentPage(@PathVariable("userId") Integer userId) { + final User user = oneLoginUserService.getUserById(userId); + final List departments = departmentService.getAllDepartments(); + return ResponseEntity.ok(ChangeDepartmentPageDto.builder() + .departments(departments) + .user(user) + .build()); + } + + @PatchMapping("/user/{id}/role") + public ResponseEntity updateRoles(@RequestBody() List roleIds, + @PathVariable("id") Integer id) { + oneLoginUserService.updateRoles(id, roleIds); + return ResponseEntity.ok("success"); + } +} \ No newline at end of file diff --git a/src/main/resources/db/migration/V1_4__update_user_tables.sql b/src/main/resources/db/migration/V1_4__update_user_tables.sql index 0bdd1c63..7486b6c3 100644 --- a/src/main/resources/db/migration/V1_4__update_user_tables.sql +++ b/src/main/resources/db/migration/V1_4__update_user_tables.sql @@ -28,8 +28,8 @@ CREATE TABLE roles ( CREATE TABLE roles_users ( roles_id int4 NOT NULL, users_gap_user_id int4 NOT NULL, - CONSTRAINT fk2mck5s7km22t2on8h2jpn44xq FOREIGN KEY (roles_id) REFERENCES roles(id), - CONSTRAINT fkhu2gdj9we2ucvgwy1qdfm5a5s FOREIGN KEY (users_gap_user_id) REFERENCES gap_users(gap_user_id) + CONSTRAINT fk2mck5s7km22t2on8h2jpn44xq FOREIGN KEY (roles_id) REFERENCES roles(id) ON DELETE CASCADE, + CONSTRAINT fkhu2gdj9we2ucvgwy1qdfm5a5s FOREIGN KEY (users_gap_user_id) REFERENCES gap_users(gap_user_id) ON DELETE CASCADE ); INSERT INTO roles (name) VALUES diff --git a/src/main/resources/db/migration/V1_5__update_role_table.sql b/src/main/resources/db/migration/V1_5__update_role_table.sql new file mode 100644 index 00000000..d97f3971 --- /dev/null +++ b/src/main/resources/db/migration/V1_5__update_role_table.sql @@ -0,0 +1,2 @@ +ALTER TABLE Roles +ADD description varchar(255) NULL; \ No newline at end of file diff --git a/src/test/java/gov/cabinetofice/gapuserservice/model/UserTest.java b/src/test/java/gov/cabinetofice/gapuserservice/model/UserTest.java index 3701a614..9838b192 100644 --- a/src/test/java/gov/cabinetofice/gapuserservice/model/UserTest.java +++ b/src/test/java/gov/cabinetofice/gapuserservice/model/UserTest.java @@ -159,7 +159,7 @@ void shouldReturnFalseWhenUserIsAnApplicant() { class hasEmail { @Test void shouldReturnTrueWhenUserHasEmail() { - final User user = User.builder().email("").build(); + final User user = User.builder().emailAddress("").build(); final boolean response = user.hasEmail(); diff --git a/src/test/java/gov/cabinetofice/gapuserservice/service/DepartmentServiceTest.java b/src/test/java/gov/cabinetofice/gapuserservice/service/DepartmentServiceTest.java new file mode 100644 index 00000000..c3951f7d --- /dev/null +++ b/src/test/java/gov/cabinetofice/gapuserservice/service/DepartmentServiceTest.java @@ -0,0 +1,47 @@ +package gov.cabinetofice.gapuserservice.service; + +import gov.cabinetofice.gapuserservice.dto.DepartmentDto; +import gov.cabinetofice.gapuserservice.mappers.DepartmentMapper; +import gov.cabinetofice.gapuserservice.model.Department; +import gov.cabinetofice.gapuserservice.repository.DepartmentRepository; +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 java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class DepartmentServiceTest { + @Mock + private DepartmentRepository departmentRepository; + + @Mock + private DepartmentMapper departmentMapper; + + @InjectMocks + private DepartmentService departmentService; + + @Test + void testGetAllDepartments() { + Department department1 = Department.builder().id(1).build(); + Department department2 = Department.builder().id(2).build(); + List departments = List.of(department1, department2); + DepartmentDto departmentDto1 = DepartmentDto.builder().id("1").build(); + DepartmentDto departmentDto2 = DepartmentDto.builder().id("2").build(); + List expectedDepartments = List.of(departmentDto1, departmentDto2); + + when(departmentRepository.findAll()).thenReturn(departments); + when(departmentMapper.departmentToDepartmentDto(department1)).thenReturn(departmentDto1); + when(departmentMapper.departmentToDepartmentDto(department2)).thenReturn(departmentDto2); + + List result = departmentService.getAllDepartments(); + + assertEquals(expectedDepartments, result); + } + +} diff --git a/src/test/java/gov/cabinetofice/gapuserservice/service/OneLoginServiceTest.java b/src/test/java/gov/cabinetofice/gapuserservice/service/OneLoginServiceTest.java index 7430ec52..6e6b8bcd 100644 --- a/src/test/java/gov/cabinetofice/gapuserservice/service/OneLoginServiceTest.java +++ b/src/test/java/gov/cabinetofice/gapuserservice/service/OneLoginServiceTest.java @@ -118,7 +118,7 @@ void shouldReturnUserInfo() throws IOException, JSONException { ",\"email_verified\":\"true\",\"email\":\"test.user@email.com\"}"; OneLoginUserInfoDto expectedResponse = OneLoginUserInfoDto.builder() .sub("urn:fdc:gov.uk:2022:jhkdasy7dal7dadhadasdas") - .email("test.user@email.com") + .emailAddress("test.user@email.com") .build(); Map headers = new HashMap<>(); @@ -216,7 +216,7 @@ void shouldSaveUserWithSubAndEmailWhenUserIsCreated() { final ArgumentCaptor userArgumentCaptor = ArgumentCaptor.forClass(User.class); verify(userRepository).save(userArgumentCaptor.capture()); Assertions.assertEquals("sub", userArgumentCaptor.getValue().getSub()); - Assertions.assertEquals("test@email.com", userArgumentCaptor.getValue().getEmail()); + Assertions.assertEquals("test@email.com", userArgumentCaptor.getValue().getEmailAddress()); } @Test @@ -232,9 +232,9 @@ class addSubToUser { @Test void shouldSaveUserWithSubWhenUserExists() { final String email = "email@test.com"; - final User user = User.builder().id(1).email(email).build(); + final User user = User.builder().gapUserId(1).emailAddress(email).build(); - when(userRepository.findByEmail(email)).thenReturn(Optional.of(user)); + when(userRepository.findByEmailAddress(email)).thenReturn(Optional.of(user)); oneLoginService.addSubToUser("sub", email); @@ -245,7 +245,7 @@ void shouldSaveUserWithSubWhenUserExists() { @Test void shouldThrowExceptionWhenUserDoesNotExist() { - when(userRepository.findByEmail(anyString())).thenReturn(Optional.empty()); + when(userRepository.findByEmailAddress(anyString())).thenReturn(Optional.empty()); Assertions.assertThrows(UserNotFoundException.class, () -> oneLoginService.addSubToUser("", "")); } diff --git a/src/test/java/gov/cabinetofice/gapuserservice/service/RoleServiceTest.java b/src/test/java/gov/cabinetofice/gapuserservice/service/RoleServiceTest.java new file mode 100644 index 00000000..4c564e5c --- /dev/null +++ b/src/test/java/gov/cabinetofice/gapuserservice/service/RoleServiceTest.java @@ -0,0 +1,45 @@ +package gov.cabinetofice.gapuserservice.service; + +import gov.cabinetofice.gapuserservice.dto.RoleDto; +import gov.cabinetofice.gapuserservice.mappers.RoleMapper; +import gov.cabinetofice.gapuserservice.model.Role; +import gov.cabinetofice.gapuserservice.model.RoleEnum; +import gov.cabinetofice.gapuserservice.repository.RoleRepository; +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 java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class RoleServiceTest { + @InjectMocks + private RoleService roleService; + @Mock + private RoleRepository roleRepository; + @Mock + RoleMapper roleMapper; + @Test + void getAllRoles() { + Role role1 = Role.builder().id(1).name(RoleEnum.FIND).description("a desc").build(); + Role role2 = Role.builder().id(2).name(RoleEnum.APPLICANT).description("a desc2").build(); + List mockRoles = List.of(role1, role2); + RoleDto roleDto1 = RoleDto.builder().id("1").name("FIND").description("a desc").build(); + RoleDto roleDto2 = RoleDto.builder().id("2").name("APPLICANT").description("a desc2").build(); + List expectedRoleDtos = List.of(roleDto1, roleDto2); + + when(roleRepository.findAll()).thenReturn(mockRoles); + when(roleMapper.roleToRoleDto(role1)).thenReturn(roleDto1); + when(roleMapper.roleToRoleDto(role2)).thenReturn(roleDto2); + + List actualRoleDtos = roleService.getAllRoles(); + + assertThat(expectedRoleDtos).isEqualTo(actualRoleDtos);; + } + +} diff --git a/src/test/java/gov/cabinetofice/gapuserservice/service/user/OneLoginUserServiceTest.java b/src/test/java/gov/cabinetofice/gapuserservice/service/user/OneLoginUserServiceTest.java new file mode 100644 index 00000000..a867675b --- /dev/null +++ b/src/test/java/gov/cabinetofice/gapuserservice/service/user/OneLoginUserServiceTest.java @@ -0,0 +1,163 @@ +package gov.cabinetofice.gapuserservice.service.user; + +import gov.cabinetofice.gapuserservice.dto.UserDto; +import gov.cabinetofice.gapuserservice.exceptions.DepartmentNotFoundException; +import gov.cabinetofice.gapuserservice.exceptions.UserNotFoundException; +import gov.cabinetofice.gapuserservice.mappers.UserMapper; +import gov.cabinetofice.gapuserservice.model.Department; +import gov.cabinetofice.gapuserservice.model.Role; +import gov.cabinetofice.gapuserservice.model.RoleEnum; +import gov.cabinetofice.gapuserservice.model.User; +import gov.cabinetofice.gapuserservice.repository.DepartmentRepository; +import gov.cabinetofice.gapuserservice.repository.RoleRepository; +import gov.cabinetofice.gapuserservice.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.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class OneLoginUserServiceTest { + + @InjectMocks + private OneLoginUserService oneLoginUserService; + + @Mock + private UserRepository userRepository; + + @Mock + private DepartmentRepository departmentRepository; + + @Mock + private RoleRepository roleRepository; + + @Mock + private UserMapper userMapper; + + @Test + void shouldReturnUpdatedUserWhenValidIdAndRolesAreGiven() { + Integer userId = 1; + List newRoles = List.of(1, 2); + List currentUserRoles = spy(List.of(Role.builder().name(RoleEnum.FIND).id(1).build())); + User user = spy(User.builder().gapUserId(1).sub("sub").roles(currentUserRoles).build()); + Role role1 = Role.builder().id(1).name(RoleEnum.FIND).description("a desc").build(); + Role role2 = Role.builder().id(2).name(RoleEnum.APPLICANT).description("a desc 2").build(); + + doReturn(user).when(user).removeAllRoles(); + doNothing().when(user).addRole(role2); + doNothing().when(user).addRole(role1); + + when(userRepository.findById(userId)).thenReturn(Optional.of(user)); + when(roleRepository.findById(1)).thenReturn(Optional.of(role1)); + when(roleRepository.findById(2)).thenReturn(Optional.of(role2)); + + User updatedUser = oneLoginUserService.updateRoles(1, newRoles); + + Mockito.verify(roleRepository, times(2)).findById(anyInt()); + Mockito.verify(userRepository, times(1)).save(user); + assertThat(user).isEqualTo(updatedUser); + } + + @Test + void shouldCallUserRepository() { + oneLoginUserService.getUserCount(); + verify(userRepository, times(1)).count(); + } + + @Test + void shouldReturnUserWhenValidIdIsGiven() { + + User mockedUser = User.builder().gapUserId(1).build(); + when(userRepository.findById(1)).thenReturn(Optional.of(mockedUser)); + User result = oneLoginUserService.getUserById(1); + + assertThat(result).isNotNull(); + assertThat(result).isEqualTo(mockedUser); + verify(userRepository, times(1)).findById(1); + } + + @Test + void shouldThrowUserNotFoundExceptionWhenInValidIdIsGiven() { + when(userRepository.findById(anyInt())).thenReturn(Optional.empty()); + assertThrows(UserNotFoundException.class, () -> oneLoginUserService.getUserById(100)); + } + + @Test + void shouldReturnPaginatedUsers() { + User user1 = User.builder().gapUserId(1).build(); + User user2 = User.builder().gapUserId(2).build(); + List users = Arrays.asList(user1, user2); + + Pageable pageable = mock(Pageable.class); + Page userPage = new PageImpl<>(users, pageable, users.size()); + + when(userRepository.findAll(pageable)).thenReturn(userPage); + + UserDto userDto1 = UserDto.builder().gapUserId("1").build(); + UserDto userDto2 = UserDto.builder().gapUserId("2").build(); + when(userMapper.userToUserDto(user1)).thenReturn(userDto1); + when(userMapper.userToUserDto(user2)).thenReturn(userDto2); + + List result = oneLoginUserService.getPaginatedUsers(pageable); + + assertEquals(2, result.size()); + assertEquals(userDto1, result.get(0)); + assertEquals(userDto2, result.get(1)); + } + + @Test + void shouldReturnUpdatedUserWhenValidUserAndDepartmentIsGiven() { + User user = User.builder().gapUserId(1).build(); + Department department = new Department(); + Integer userId = 1; + Integer departmentId = 2; + + when(userRepository.findById(userId)).thenReturn(Optional.of(user)); + when(departmentRepository.findById(departmentId)).thenReturn(Optional.of(department)); + when(userRepository.save(user)).thenReturn(user); + User result = oneLoginUserService.updateDepartment(userId, departmentId); + + assertEquals(department, user.getDepartment()); + verify(userRepository).save(user); + assertEquals(user, result); + } + + @Test + void testUpdateDepartment_UserNotFound() { + Integer userId = 1; + Integer departmentId = 2; + + when(userRepository.findById(userId)).thenReturn(Optional.empty()); + + assertThrows(UserNotFoundException.class, () -> oneLoginUserService.updateDepartment(userId, departmentId)); + } + + @Test + void testUpdateDepartment_DepartmentNotFound() { + User user = new User(); + Integer userId = 1; + Integer departmentId = 2; + + when(userRepository.findById(userId)).thenReturn(Optional.of(user)); + + when(departmentRepository.findById(departmentId)).thenReturn(Optional.empty()); + + assertThrows(DepartmentNotFoundException.class, () -> oneLoginUserService.updateDepartment(userId, departmentId)); + } + +} diff --git a/src/test/java/gov/cabinetofice/gapuserservice/web/LoginControllerV2Test.java b/src/test/java/gov/cabinetofice/gapuserservice/web/LoginControllerV2Test.java index c248eb4f..097dad05 100644 --- a/src/test/java/gov/cabinetofice/gapuserservice/web/LoginControllerV2Test.java +++ b/src/test/java/gov/cabinetofice/gapuserservice/web/LoginControllerV2Test.java @@ -137,7 +137,7 @@ void beforeEach() { when(oneLoginService.getUserInfo(null)) .thenReturn(OneLoginUserInfoDto.builder() .sub("sub") - .email("email") + .emailAddress("email") .build()); when(customJwtService.generateToken(any())) .thenReturn("a-custom-valid-token"); @@ -172,7 +172,7 @@ void shouldCreateNewUser_WhenNoUserFound() { void shouldDoNothing_WhenUserFoundWithSub() { final HttpServletResponse response = Mockito.spy(new MockHttpServletResponse()); final Optional redirectUrl = Optional.of("redirectUrl"); - final User user = User.builder().sub("sub").email("email").roles(List.of( + final User user = User.builder().sub("sub").emailAddress("email").roles(List.of( Role.builder().name(RoleEnum.APPLICANT).build() )).build(); @@ -197,7 +197,7 @@ void shouldDoNothing_WhenUserFoundWithSub() { void shouldUpdateUser_WhenUserFoundWithoutSub_AndIsAdmin() { final HttpServletResponse response = Mockito.spy(new MockHttpServletResponse()); final Optional redirectUrl = Optional.of("redirectUrl"); - final User user = User.builder().email("email").roles(List.of( + final User user = User.builder().emailAddress("email").roles(List.of( Role.builder().name(RoleEnum.ADMIN).build(), Role.builder().name(RoleEnum.APPLICANT).build(), Role.builder().name(RoleEnum.FIND).build() @@ -209,7 +209,7 @@ void shouldUpdateUser_WhenUserFoundWithoutSub_AndIsAdmin() { final RedirectView methodResponse = loginController.redirectAfterLogin(redirectUrl, response, "a-custom-valid-token"); verify(oneLoginService).addSubToUser("sub", "email"); - assertThat(methodResponse.getUrl()).isEqualTo("adminBaseUrl/dashboard"); + assertThat(methodResponse.getUrl()).isEqualTo("adminBaseUrl"); verify(response).addCookie(customJwtCookie); final Map claims = new HashMap<>(); @@ -224,7 +224,7 @@ void shouldUpdateUser_WhenUserFoundWithoutSub_AndIsAdmin() { void shouldUpdateUser_WhenUserFoundWithoutSub_AndIsSuperAdmin() { final HttpServletResponse response = Mockito.spy(new MockHttpServletResponse()); final Optional redirectUrl = Optional.of("redirectUrl"); - final User user = User.builder().email("email").roles(List.of( + final User user = User.builder().emailAddress("email").roles(List.of( Role.builder().name(RoleEnum.SUPER_ADMIN).build(), Role.builder().name(RoleEnum.ADMIN).build(), Role.builder().name(RoleEnum.APPLICANT).build(), @@ -251,7 +251,7 @@ void shouldUpdateUser_WhenUserFoundWithoutSub_AndIsSuperAdmin() { void shouldGoToMigrateDataPage_WhenUserFoundWithoutSub_AndIsAnApplicant() { final HttpServletResponse response = Mockito.spy(new MockHttpServletResponse()); final Optional redirectUrl = Optional.of("redirectUrl"); - final User user = User.builder().email("email").roles(List.of( + final User user = User.builder().emailAddress("email").roles(List.of( Role.builder().name(RoleEnum.APPLICANT).build(), Role.builder().name(RoleEnum.FIND).build() )).build(); diff --git a/src/test/java/gov/cabinetofice/gapuserservice/web/RoleControllerTest.java b/src/test/java/gov/cabinetofice/gapuserservice/web/RoleControllerTest.java new file mode 100644 index 00000000..687cc02c --- /dev/null +++ b/src/test/java/gov/cabinetofice/gapuserservice/web/RoleControllerTest.java @@ -0,0 +1,33 @@ +package gov.cabinetofice.gapuserservice.web; + +import gov.cabinetofice.gapuserservice.dto.RoleDto; +import gov.cabinetofice.gapuserservice.service.RoleService; +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.http.ResponseEntity; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class RoleControllerTest { + @InjectMocks + private RoleController controller; + @Mock + private RoleService roleService; + + @Test + void getAllRoles_returnsArrayOfRoles() { + final List roles = List.of(RoleDto.builder().name("FIND").id("1").description("a desc").build()); + when(roleService.getAllRoles()) + .thenReturn(roles); + final ResponseEntity> methodResponse = controller.getAll(); + + assertThat(methodResponse.getBody()).isSameAs(roles); + } +} \ No newline at end of file diff --git a/src/test/java/gov/cabinetofice/gapuserservice/web/SuperAdminControllerTest.java b/src/test/java/gov/cabinetofice/gapuserservice/web/SuperAdminControllerTest.java new file mode 100644 index 00000000..16ef6a08 --- /dev/null +++ b/src/test/java/gov/cabinetofice/gapuserservice/web/SuperAdminControllerTest.java @@ -0,0 +1,65 @@ +package gov.cabinetofice.gapuserservice.web; + +import gov.cabinetofice.gapuserservice.dto.DepartmentDto; +import gov.cabinetofice.gapuserservice.dto.RoleDto; +import gov.cabinetofice.gapuserservice.dto.SuperAdminDashboardPageDto; +import gov.cabinetofice.gapuserservice.dto.UserDto; +import gov.cabinetofice.gapuserservice.service.DepartmentService; +import gov.cabinetofice.gapuserservice.service.RoleService; +import gov.cabinetofice.gapuserservice.service.user.OneLoginUserService; +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.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.util.List; +import java.util.Objects; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class SuperAdminControllerTest { + @Mock + private DepartmentService departmentService; + + @Mock + private RoleService roleService; + + @Mock + private OneLoginUserService oneLoginUserService; + + @InjectMocks + private SuperAdminController superAdminController; + + @Test + void shouldReturnSuperAdminDashboardDto() { + Pageable pagination = mock(Pageable.class); + List departments = List.of(DepartmentDto.builder().id("1").build(), + DepartmentDto.builder().id("2").build()); + List roles = List.of(RoleDto.builder().id("1").build(), RoleDto.builder().id("2").build()); + List users = List.of(UserDto.builder().gapUserId("1").build(), + UserDto.builder().gapUserId("2").build()); + + when(departmentService.getAllDepartments()).thenReturn(departments); + when(roleService.getAllRoles()).thenReturn(roles); + when(oneLoginUserService.getPaginatedUsers(pagination)).thenReturn(users); + when(oneLoginUserService.getUserCount()).thenReturn(2L); + + ResponseEntity result = + superAdminController.superAdminDashboard(pagination); + + assertEquals(HttpStatus.OK, result.getStatusCode()); + + SuperAdminDashboardPageDto responseDto = result.getBody(); + assertEquals(departments, Objects.requireNonNull(responseDto).getDepartments()); + assertEquals(roles, responseDto.getRoles()); + assertEquals(users, responseDto.getUsers()); + assertEquals(2L, responseDto.getUserCount()); + } +} \ No newline at end of file diff --git a/src/test/java/gov/cabinetofice/gapuserservice/web/UserControllerTest.java b/src/test/java/gov/cabinetofice/gapuserservice/web/UserControllerTest.java new file mode 100644 index 00000000..1220d396 --- /dev/null +++ b/src/test/java/gov/cabinetofice/gapuserservice/web/UserControllerTest.java @@ -0,0 +1,68 @@ +package gov.cabinetofice.gapuserservice.web; +import gov.cabinetofice.gapuserservice.dto.ChangeDepartmentPageDto; +import gov.cabinetofice.gapuserservice.dto.DepartmentDto; +import gov.cabinetofice.gapuserservice.model.Role; +import gov.cabinetofice.gapuserservice.model.RoleEnum; +import gov.cabinetofice.gapuserservice.model.User; +import gov.cabinetofice.gapuserservice.service.DepartmentService; +import gov.cabinetofice.gapuserservice.service.user.OneLoginUserService; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.ResponseEntity; +import java.util.List; +import java.util.Objects; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class UserControllerTest { + @InjectMocks + private UserController controller; + @Mock + private OneLoginUserService oneLoginUserService; + + @Mock + private DepartmentService departmentService; + @Test + void updateRolesForUserId() { + final ResponseEntity methodResponse = controller.updateRoles(List.of(1,2), 1); + + assertThat(methodResponse).isEqualTo(ResponseEntity.ok("success")); + } + @Test + void shouldReturnUserWhenValidIdIsGiven() { + User mockUser = User.builder().sub("1").gapUserId(1) + .roles(List.of(Role.builder() + .name(RoleEnum.FIND) + .description("desc").build())) + .emailAddress("test@gov.uk").build(); + when(oneLoginUserService.getUserById(1)).thenReturn(mockUser); + final ResponseEntity methodResponse = controller.getUserById(1); + + assertThat(methodResponse.getBody()).isSameAs(mockUser); + } + + @Test + void shouldReturnChangeDepartmentPageDtoWhenValidIdIsGiven() { + User mockUser = User.builder().sub("1").gapUserId(1) + .roles(List.of(Role.builder() + .name(RoleEnum.FIND) + .description("desc").build())) + .emailAddress("test@gov.uk").build(); + when(oneLoginUserService.getUserById(1)).thenReturn(mockUser); + when(departmentService.getAllDepartments()) + .thenReturn(List.of(DepartmentDto.builder().id("1").name("dept").build())); + + ResponseEntity result = controller.getChangeDepartmentPage(1); + + verify(oneLoginUserService, times(1)).getUserById(1); + verify(departmentService, times(1)).getAllDepartments(); + assertThat(Objects.requireNonNull(result.getBody()).getDepartments().get(0).getId()).isEqualTo("1"); + } +} \ No newline at end of file