diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c3c0bc83..5f620681 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -6,19 +6,19 @@ on:
- "**"
jobs:
build:
- name: Maven Build
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v3
-
- - name: Setup Java 17
- uses: actions/setup-java@v3
- with:
- java-version: 17
- distribution: "temurin"
-
- - name: Build with Maven
- run: mvn verify --file skill-tree/pom.xml -Pgit-build-profile -Dskip-tests=true
+ name: Maven Build
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Setup Java 17
+ uses: actions/setup-java@v3
+ with:
+ java-version: 17
+ distribution: "temurin"
+
+ - name: Build with Maven
+ run: mvn verify --file skill-tree/pom.xml -P git-build-profile -Dskip-tests=true
unit-test:
name: Unit Tests
diff --git a/skill-tree/pom.xml b/skill-tree/pom.xml
index ac132714..d53dee5a 100644
--- a/skill-tree/pom.xml
+++ b/skill-tree/pom.xml
@@ -90,6 +90,11 @@
org.springframework.boot
spring-boot-starter-security
+
+ org.springframework.security
+ spring-security-test
+ test
+
@@ -214,7 +219,7 @@
${skip-ut}
${skip-tests}
- **/unit/*.java
+ **/unit/**/*.java
@@ -226,7 +231,7 @@
${skip-tests}
- **/integration/*.java
+ **/integration/**/*.java
diff --git a/skill-tree/src/main/java/com/RDS/skilltree/apis/SkillsApi.java b/skill-tree/src/main/java/com/RDS/skilltree/apis/SkillsApi.java
index c829e997..c57fd561 100644
--- a/skill-tree/src/main/java/com/RDS/skilltree/apis/SkillsApi.java
+++ b/skill-tree/src/main/java/com/RDS/skilltree/apis/SkillsApi.java
@@ -36,12 +36,14 @@ public ResponseEntity> getAll() {
@GetMapping("/requests")
public ResponseEntity getAllRequests(
- @RequestParam(value = "status", required = false) UserSkillStatusEnum status) {
+ @RequestParam(name = "status", required = false) UserSkillStatusEnum status,
+ @RequestParam(name = "dev", required = false, defaultValue = "false") boolean isDev) {
+
if (status != null) {
- return ResponseEntity.ok(skillService.getRequestsByStatus(status));
+ return ResponseEntity.ok(skillService.getRequestsByStatus(status, isDev));
}
- return ResponseEntity.ok(skillService.getAllRequests());
+ return ResponseEntity.ok(skillService.getAllRequests(isDev));
}
@PostMapping("/requests/{skillId}/action")
@@ -57,7 +59,7 @@ public ResponseEntity> approveRejectSkillRequest(
@PostMapping
@AuthorizedRoles({UserRoleEnum.SUPERUSER})
public ResponseEntity create(@Valid @RequestBody CreateSkillViewModel skill) {
- return ResponseEntity.ok(skillService.create(skill));
+ return ResponseEntity.status(HttpStatus.CREATED).body(skillService.create(skill));
}
@GetMapping("/{id}/endorsements")
diff --git a/skill-tree/src/main/java/com/RDS/skilltree/exceptions/EndorsementAlreadyExistsException.java b/skill-tree/src/main/java/com/RDS/skilltree/exceptions/EndorsementAlreadyExistsException.java
new file mode 100644
index 00000000..b6012021
--- /dev/null
+++ b/skill-tree/src/main/java/com/RDS/skilltree/exceptions/EndorsementAlreadyExistsException.java
@@ -0,0 +1,7 @@
+package com.RDS.skilltree.exceptions;
+
+public class EndorsementAlreadyExistsException extends RuntimeException {
+ public EndorsementAlreadyExistsException(String message) {
+ super(message);
+ }
+}
diff --git a/skill-tree/src/main/java/com/RDS/skilltree/exceptions/GlobalExceptionHandler.java b/skill-tree/src/main/java/com/RDS/skilltree/exceptions/GlobalExceptionHandler.java
index e5abe97c..3af16875 100644
--- a/skill-tree/src/main/java/com/RDS/skilltree/exceptions/GlobalExceptionHandler.java
+++ b/skill-tree/src/main/java/com/RDS/skilltree/exceptions/GlobalExceptionHandler.java
@@ -2,10 +2,7 @@
import com.RDS.skilltree.utils.GenericResponse;
import jakarta.validation.ConstraintViolationException;
-import java.time.LocalDateTime;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.websocket.AuthenticationException;
import org.springframework.http.HttpStatus;
@@ -16,16 +13,15 @@
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
-import org.springframework.web.context.request.WebRequest;
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
+
@ExceptionHandler({NoEntityException.class})
public ResponseEntity> handleNoEntityException(NoEntityException ex) {
- log.error("NoEntityException - Error : {}", ex.getMessage(), ex);
- return ResponseEntity.status(HttpStatus.NOT_FOUND)
- .body(new GenericResponse<>(null, ex.getMessage()));
+ log.error("NoEntityException - Error : {}", ex.getMessage());
+ return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new GenericResponse<>(ex.getMessage()));
}
@ExceptionHandler({AuthenticationException.class, InsufficientAuthenticationException.class})
@@ -40,25 +36,21 @@ public ResponseEntity> handleInvalidBearerTokenException
@ExceptionHandler({AccessDeniedException.class})
public ResponseEntity> handleAccessDeniedException(
AccessDeniedException ex) {
- return ResponseEntity.status(HttpStatus.FORBIDDEN)
- .body(new GenericResponse<>(null, ex.getMessage()));
+ return ResponseEntity.status(HttpStatus.FORBIDDEN).body(new GenericResponse<>(ex.getMessage()));
}
@ExceptionHandler({EntityAlreadyExistsException.class})
public ResponseEntity> handleEntityAlreadyExistsException(
EntityAlreadyExistsException ex) {
- log.error("EntityAlreadyExistsException - Error : {}", ex.getMessage(), ex);
- return ResponseEntity.status(HttpStatus.CONFLICT)
- .body(new GenericResponse<>(null, ex.getMessage()));
+ log.error("EntityAlreadyExistsException - Error : {}", ex.getMessage());
+ return ResponseEntity.status(HttpStatus.CONFLICT).body(new GenericResponse<>(ex.getMessage()));
}
@ExceptionHandler({RuntimeException.class})
public ResponseEntity> handleRuntimeException(RuntimeException ex) {
- log.error("Runtime Exception - Error : {}", ex.getMessage(), ex);
+ log.error("Runtime Exception - Error : {}", ex.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
- .body(
- new GenericResponse<>(
- null, "Runtime Exception - Something went wrong, please try again."));
+ .body(new GenericResponse<>("Runtime Exception - Something went wrong, please try again."));
}
@ExceptionHandler({MethodArgumentNotValidException.class})
@@ -73,85 +65,81 @@ public ResponseEntity> handleMethodArgumentNotValidExcep
}
log.error("MethodArgumentNotValidException Exception - Error : {}", ex.getMessage(), ex);
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
- .body(new GenericResponse<>(null, errorString.toString().trim()));
+ .body(new GenericResponse<>(errorString.toString().trim()));
}
@ExceptionHandler({Exception.class})
public ResponseEntity> handleException(Exception ex) {
- log.error("Exception - Error : {}", ex.getMessage(), ex);
+ log.error("Exception - Error : {}", ex.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
- .body(new GenericResponse<>(null, "Something unexpected happened, please try again."));
+ .body(new GenericResponse<>("Something unexpected happened, please try again."));
}
@ExceptionHandler({InvalidParameterException.class})
public ResponseEntity> handleException(InvalidParameterException ex) {
- log.error("Exception - Error : {}", ex.getMessage(), ex);
+ log.error("InvalidParameterException - Error : {}", ex.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
- .body(new GenericResponse<>(null, ex.getMessage()));
+ .body(new GenericResponse<>(ex.getMessage()));
}
@ExceptionHandler({ConstraintViolationException.class})
public ResponseEntity> handleException(ConstraintViolationException ex) {
- log.error("Exception - Error : {}", ex.getMessage(), ex);
+ log.error("ConstraintViolationException - Error : {}", ex.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
- .body(new GenericResponse<>(null, ex.getMessage()));
+ .body(new GenericResponse<>(ex.getMessage()));
}
@ExceptionHandler(UserNotFoundException.class)
- public ResponseEntity> handleUserNotFoundException(
- UserNotFoundException ex, WebRequest request) {
- log.error("Exception - Error : {}", ex.getMessage(), ex);
- return new ResponseEntity<>(new GenericResponse<>(null, ex.getMessage()), HttpStatus.NOT_FOUND);
+ public ResponseEntity> handleUserNotFoundException(UserNotFoundException ex) {
+ log.error("UserNotFoundException - Error : {}", ex.getMessage());
+ return new ResponseEntity<>(new GenericResponse<>(ex.getMessage()), HttpStatus.NOT_FOUND);
}
@ExceptionHandler(SkillAlreadyExistsException.class)
- public ResponseEntity> handleSkillAlreadyExistsException(
- SkillAlreadyExistsException ex, WebRequest request) {
- log.error("Exception - Error : {}", ex.getMessage(), ex);
- return new ResponseEntity<>(new GenericResponse<>(null, ex.getMessage()), HttpStatus.CONFLICT);
+ public ResponseEntity> handleSkillAlreadyExistsException(SkillAlreadyExistsException ex) {
+ log.error("SkillAlreadyExistsException - Error : {}", ex.getMessage());
+ return new ResponseEntity<>(new GenericResponse<>(ex.getMessage()), HttpStatus.CONFLICT);
}
@ExceptionHandler(SelfEndorsementNotAllowedException.class)
public ResponseEntity> handleSelfEndorsementNotAllowedException(
- SelfEndorsementNotAllowedException ex, WebRequest request) {
- log.error("Exception - Error : {}", ex.getMessage(), ex);
+ SelfEndorsementNotAllowedException ex) {
+ log.error("SelfEndorsementNotAllowedException - Error : {}", ex.getMessage());
return new ResponseEntity<>(
- new GenericResponse<>(null, ex.getMessage()), HttpStatus.METHOD_NOT_ALLOWED);
+ new GenericResponse<>(ex.getMessage()), HttpStatus.METHOD_NOT_ALLOWED);
}
@ExceptionHandler(SkillNotFoundException.class)
- public ResponseEntity> handleSkillNotFoundException(
- SkillNotFoundException ex, WebRequest request) {
- log.error("Exception - Error : {}", ex.getMessage(), ex);
- return new ResponseEntity<>(new GenericResponse<>(null, ex.getMessage()), HttpStatus.NOT_FOUND);
+ public ResponseEntity> handleSkillNotFoundException(SkillNotFoundException ex) {
+ log.error("SkillNotFoundException - Error : {}", ex.getMessage());
+ return new ResponseEntity<>(new GenericResponse<>(ex.getMessage()), HttpStatus.NOT_FOUND);
}
@ExceptionHandler(EndorsementNotFoundException.class)
- public ResponseEntity> handleEndorsementNotException(
- EndorsementNotFoundException ex, WebRequest request) {
- log.error("Exception - Error : {}", ex.getMessage(), ex);
- return new ResponseEntity<>(new GenericResponse<>(null, ex.getMessage()), HttpStatus.NOT_FOUND);
+ public ResponseEntity> handleEndorsementNotFoundException(EndorsementNotFoundException ex) {
+ log.error("EndorsementNotFoundException - Error : {}", ex.getMessage());
+ return new ResponseEntity<>(new GenericResponse<>(ex.getMessage()), HttpStatus.NOT_FOUND);
}
@ExceptionHandler(ForbiddenException.class)
- public ResponseEntity> handleForbiddenException(ForbiddenException ex, WebRequest request) {
- log.error("Exception - Error : {}", ex.getMessage(), ex);
- return new ResponseEntity<>(new GenericResponse<>(null, ex.getMessage()), HttpStatus.FORBIDDEN);
+ public ResponseEntity> handleForbiddenException(ForbiddenException ex) {
+ log.error("ForbiddenException - Error : {}", ex.getMessage());
+ return new ResponseEntity<>(new GenericResponse<>(ex.getMessage()), HttpStatus.FORBIDDEN);
}
@ExceptionHandler(InternalServerErrorException.class)
- public ResponseEntity> handleInternalServerErrorException(
- InternalServerErrorException ex, WebRequest request) {
+ public ResponseEntity> handleInternalServerErrorException(InternalServerErrorException ex) {
log.error("Internal Server Error", ex);
- // Create a more specific error message based on the exception type or cause
String errorMessage = "An unexpected error occurred.";
+ return new ResponseEntity<>(
+ new GenericResponse<>(errorMessage), HttpStatus.INTERNAL_SERVER_ERROR);
+ }
- // Consider adding more details to the response for debugging
- Map errorDetails = new HashMap<>();
- errorDetails.put("timestamp", LocalDateTime.now());
- errorDetails.put("message", errorMessage);
- errorDetails.put("details", ex.getMessage()); // Include exception details for debugging
-
- return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR);
+ @ExceptionHandler(EndorsementAlreadyExistsException.class)
+ public ResponseEntity> handleEndorsementAlreadyExistsException(
+ EndorsementAlreadyExistsException ex) {
+ log.error("EndorsementAlreadyExistsException - Error : {}", ex.getMessage());
+ return new ResponseEntity<>(
+ new GenericResponse<>(ex.getMessage()), HttpStatus.METHOD_NOT_ALLOWED);
}
}
diff --git a/skill-tree/src/main/java/com/RDS/skilltree/repositories/EndorsementRepository.java b/skill-tree/src/main/java/com/RDS/skilltree/repositories/EndorsementRepository.java
index 9b8d3c2f..6ce0f5f9 100644
--- a/skill-tree/src/main/java/com/RDS/skilltree/repositories/EndorsementRepository.java
+++ b/skill-tree/src/main/java/com/RDS/skilltree/repositories/EndorsementRepository.java
@@ -3,9 +3,20 @@
import com.RDS.skilltree.models.Endorsement;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
public interface EndorsementRepository extends JpaRepository {
List findBySkillId(Integer skillId);
List findByEndorseIdAndSkillId(String endorseId, Integer skillId);
+
+ @Query(
+ "SELECT (COUNT(*) > 0) AS exists "
+ + "FROM Endorsement e "
+ + "WHERE e.endorserId = :endorserId AND e.endorseId = :endorseId AND e.skill.id = :skillId")
+ boolean existsByEndorseIdAndEndorserIdAndSkillId(
+ @Param("endorseId") String endorseId,
+ @Param("endorserId") String endorserId,
+ @Param("skillId") Integer skillId);
}
diff --git a/skill-tree/src/main/java/com/RDS/skilltree/repositories/UserSkillRepository.java b/skill-tree/src/main/java/com/RDS/skilltree/repositories/UserSkillRepository.java
index 12868976..bdc0c9e1 100644
--- a/skill-tree/src/main/java/com/RDS/skilltree/repositories/UserSkillRepository.java
+++ b/skill-tree/src/main/java/com/RDS/skilltree/repositories/UserSkillRepository.java
@@ -13,8 +13,21 @@ public interface UserSkillRepository extends JpaRepository
List findByUserIdAndSkillId(String userId, Integer skillId);
@Query(
- "SELECT us FROM UserSkills us "
- + "JOIN Endorsement e ON us.userId = e.endorseId "
+ "SELECT us "
+ + "FROM UserSkills us JOIN Endorsement e ON us.userId = e.endorseId "
+ + "WHERE e.endorserId = :endorserId")
+ List findUserSkillsByEndorserIdLegacy(@Param("endorserId") String endorserId);
+
+ @Query(
+ "SELECT us "
+ + "FROM UserSkills us JOIN Endorsement e ON us.userId = e.endorseId AND us.skill.id = e.skill.id "
+ "WHERE e.endorserId = :endorserId")
List findUserSkillsByEndorserId(@Param("endorserId") String endorserId);
+
+ @Query(
+ "SELECT us "
+ + "FROM UserSkills us JOIN Endorsement e ON us.userId = e.endorseId AND us.skill.id = e.skill.id "
+ + "WHERE e.endorserId = :endorserId AND us.status = :status")
+ List findByStatusAndEndorserId(
+ @Param("status") UserSkillStatusEnum status, @Param("endorserId") String endorserId);
}
diff --git a/skill-tree/src/main/java/com/RDS/skilltree/services/EndorsementServiceImplementation.java b/skill-tree/src/main/java/com/RDS/skilltree/services/EndorsementServiceImplementation.java
index b5fd5a10..1c6d25c2 100644
--- a/skill-tree/src/main/java/com/RDS/skilltree/services/EndorsementServiceImplementation.java
+++ b/skill-tree/src/main/java/com/RDS/skilltree/services/EndorsementServiceImplementation.java
@@ -1,6 +1,7 @@
package com.RDS.skilltree.services;
import com.RDS.skilltree.dtos.RdsGetUserDetailsResDto;
+import com.RDS.skilltree.exceptions.EndorsementAlreadyExistsException;
import com.RDS.skilltree.exceptions.EndorsementNotFoundException;
import com.RDS.skilltree.exceptions.SelfEndorsementNotAllowedException;
import com.RDS.skilltree.exceptions.SkillNotFoundException;
@@ -12,6 +13,7 @@
import com.RDS.skilltree.repositories.SkillRepository;
import com.RDS.skilltree.repositories.UserSkillRepository;
import com.RDS.skilltree.services.external.RdsService;
+import com.RDS.skilltree.utils.Constants.ExceptionMessages;
import com.RDS.skilltree.viewmodels.CreateEndorsementViewModel;
import com.RDS.skilltree.viewmodels.EndorsementViewModel;
import com.RDS.skilltree.viewmodels.UpdateEndorsementViewModel;
@@ -63,7 +65,6 @@ public List getAllEndorsementsBySkillId(Integer skillId) {
}
@Override
- // TODO : add a check for when a endorsement is already created by a user for a particular skill.
public EndorsementViewModel create(CreateEndorsementViewModel endorsementViewModel) {
String message = endorsementViewModel.getMessage();
Integer skillId = endorsementViewModel.getSkillId();
@@ -75,19 +76,28 @@ public EndorsementViewModel create(CreateEndorsementViewModel endorsementViewMod
String endorserId = jwtDetails.getRdsUserId();
if (Objects.equals(endorseId, endorserId)) {
- log.info(
+ log.warn(
"Self endorsement not allowed, endorseId: {}, endorserId: {}", endorseId, endorserId);
- throw new SelfEndorsementNotAllowedException("Self endorsement not allowed");
+ throw new SelfEndorsementNotAllowedException(ExceptionMessages.SELF_ENDORSEMENT_NOT_ALLOWED);
}
Optional skillDetails = skillRepository.findById(skillId);
if (skillDetails.isEmpty()) {
- log.info(String.format("Skill id: %s not found", skillId));
- throw new SkillNotFoundException("Skill does not exist");
+ log.info("Skill id: {} not found", skillId);
+ throw new SkillNotFoundException(ExceptionMessages.SKILL_NOT_FOUND);
+ }
+
+ if (endorsementRepository.existsByEndorseIdAndEndorserIdAndSkillId(
+ endorseId, endorserId, skillId)) {
+ log.info(
+ "Endorsement already exists for endorseId: {}, endorserId: {}, skillId: {}",
+ endorseId,
+ endorserId,
+ skillId);
+ throw new EndorsementAlreadyExistsException(ExceptionMessages.ENDORSEMENT_ALREADY_EXISTS);
}
- // Get endorse(person being endorsed) & endorser(person endorsing) details from RDS
RdsGetUserDetailsResDto endorseDetails = rdsService.getUserDetails(endorseId);
RdsGetUserDetailsResDto endorserDetails = rdsService.getUserDetails(endorserId);
@@ -125,7 +135,7 @@ public EndorsementViewModel update(Integer endorsementId, UpdateEndorsementViewM
if (exitingEndorsement.isEmpty()) {
log.info(String.format("Endorsement with id: %s not found", endorsementId));
- throw new EndorsementNotFoundException("Endorsement not found");
+ throw new EndorsementNotFoundException(ExceptionMessages.ENDORSEMENT_NOT_FOUND);
}
Endorsement endorsement = exitingEndorsement.get();
diff --git a/skill-tree/src/main/java/com/RDS/skilltree/services/SkillService.java b/skill-tree/src/main/java/com/RDS/skilltree/services/SkillService.java
index ce87d119..618e6fdd 100644
--- a/skill-tree/src/main/java/com/RDS/skilltree/services/SkillService.java
+++ b/skill-tree/src/main/java/com/RDS/skilltree/services/SkillService.java
@@ -12,9 +12,9 @@ public interface SkillService {
SkillViewModel create(CreateSkillViewModel skill);
- SkillRequestsDto getAllRequests();
+ SkillRequestsDto getAllRequests(boolean isDev);
- SkillRequestsDto getRequestsByStatus(UserSkillStatusEnum status);
+ SkillRequestsDto getRequestsByStatus(UserSkillStatusEnum status, boolean isDev);
GenericResponse approveRejectSkillRequest(
Integer skillId, String endorseId, UserSkillStatusEnum action);
diff --git a/skill-tree/src/main/java/com/RDS/skilltree/services/SkillServiceImplementation.java b/skill-tree/src/main/java/com/RDS/skilltree/services/SkillServiceImplementation.java
index 153ea33a..e08d1923 100644
--- a/skill-tree/src/main/java/com/RDS/skilltree/services/SkillServiceImplementation.java
+++ b/skill-tree/src/main/java/com/RDS/skilltree/services/SkillServiceImplementation.java
@@ -54,7 +54,7 @@ public List getAll() {
}
@Override
- public SkillRequestsDto getAllRequests() {
+ public SkillRequestsDto getAllRequests(boolean isDev) {
JwtUser jwtDetails =
(JwtUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
@@ -66,8 +66,10 @@ public SkillRequestsDto getAllRequests() {
if (userRole.isSuper_user()) {
skillRequests = userSkillRepository.findAll();
- } else {
+ } else if (isDev) {
skillRequests = userSkillRepository.findUserSkillsByEndorserId(userId);
+ } else {
+ skillRequests = userSkillRepository.findUserSkillsByEndorserIdLegacy(userId);
}
if (skillRequests == null) {
@@ -82,8 +84,26 @@ public SkillRequestsDto getAllRequests() {
}
@Override
- public SkillRequestsDto getRequestsByStatus(UserSkillStatusEnum status) {
- List skillRequests = userSkillRepository.findByStatus(status);
+ public SkillRequestsDto getRequestsByStatus(UserSkillStatusEnum status, boolean isDev) {
+ JwtUser jwtDetails =
+ (JwtUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
+
+ RdsGetUserDetailsResDto userDetails = rdsService.getUserDetails(jwtDetails.getRdsUserId());
+ RdsUserViewModel.Roles userRole = userDetails.getUser().getRoles();
+ String userId = userDetails.getUser().getId();
+
+ List skillRequests = null;
+
+ if (!isDev || userRole.isSuper_user()) {
+ skillRequests = userSkillRepository.findByStatus(status);
+ } else {
+ skillRequests = userSkillRepository.findByStatusAndEndorserId(status, userId);
+ }
+
+ if (skillRequests == null) {
+ throw new InternalServerErrorException("Unable to fetch skill requests");
+ }
+
SkillRequestsWithUserDetailsViewModel skillRequestsWithUserDetails =
toSkillRequestsWithUserDetailsViewModel(skillRequests);
@@ -129,39 +149,38 @@ public GenericResponse approveRejectSkillRequest(
private SkillRequestsWithUserDetailsViewModel toSkillRequestsWithUserDetailsViewModel(
List skills) {
+
// store all users data that are a part of this request
- Map userDetails = new HashMap<>();
+ Map userViewModelMap = new HashMap<>();
List skillRequests =
skills.stream()
.map(
skill -> {
Integer skillId = skill.getSkill().getId();
-
String endorseId = skill.getUserId();
- // Get all endorsement for a specific skill and user Id
List endorsements =
endorsementRepository.findByEndorseIdAndSkillId(endorseId, skillId);
- if (!userDetails.containsKey(endorseId)) {
+ if (!userViewModelMap.containsKey(endorseId)) {
RdsGetUserDetailsResDto endorseRdsDetails =
rdsService.getUserDetails(endorseId);
UserViewModel endorseDetails =
getUserModalFromRdsDetails(endorseId, endorseRdsDetails);
- userDetails.put(endorseId, endorseDetails);
+ userViewModelMap.put(endorseId, endorseDetails);
}
+ // Add details of the endorsers
endorsements.forEach(
endorsement -> {
String endorserId = endorsement.getEndorserId();
-
- if (!userDetails.containsKey(endorserId)) {
+ if (!userViewModelMap.containsKey(endorserId)) {
RdsGetUserDetailsResDto endorserRdsDetails =
rdsService.getUserDetails(endorserId);
UserViewModel endorserDetails =
getUserModalFromRdsDetails(endorserId, endorserRdsDetails);
- userDetails.put(endorserId, endorserDetails);
+ userViewModelMap.put(endorserId, endorserDetails);
}
});
@@ -171,7 +190,7 @@ private SkillRequestsWithUserDetailsViewModel toSkillRequestsWithUserDetailsView
return SkillRequestsWithUserDetailsViewModel.builder()
.skillRequests(skillRequests)
- .users(userDetails.values().stream().toList())
+ .users(userViewModelMap.values().stream().toList())
.build();
}
diff --git a/skill-tree/src/main/java/com/RDS/skilltree/utils/Constants.java b/skill-tree/src/main/java/com/RDS/skilltree/utils/Constants.java
new file mode 100644
index 00000000..da443a25
--- /dev/null
+++ b/skill-tree/src/main/java/com/RDS/skilltree/utils/Constants.java
@@ -0,0 +1,12 @@
+package com.RDS.skilltree.utils;
+
+public class Constants {
+ private Constants() {}
+
+ public static final class ExceptionMessages {
+ public static final String SELF_ENDORSEMENT_NOT_ALLOWED = "Self endorsement not allowed";
+ public static final String SKILL_NOT_FOUND = "Skill does not exist";
+ public static final String ENDORSEMENT_ALREADY_EXISTS = "Endorsement already exists";
+ public static final String ENDORSEMENT_NOT_FOUND = "Endorsement not found";
+ }
+}
diff --git a/skill-tree/src/main/java/com/RDS/skilltree/utils/GenericResponse.java b/skill-tree/src/main/java/com/RDS/skilltree/utils/GenericResponse.java
index 70d73df3..83a8a3ce 100644
--- a/skill-tree/src/main/java/com/RDS/skilltree/utils/GenericResponse.java
+++ b/skill-tree/src/main/java/com/RDS/skilltree/utils/GenericResponse.java
@@ -14,4 +14,8 @@
public class GenericResponse {
private T data;
private String message;
+
+ public GenericResponse(String message) {
+ this.message = message;
+ }
}
diff --git a/skill-tree/src/main/resources/application-production.properties b/skill-tree/src/main/resources/application-production.properties
index 26d4250e..69f872f5 100644
--- a/skill-tree/src/main/resources/application-production.properties
+++ b/skill-tree/src/main/resources/application-production.properties
@@ -1 +1,2 @@
-cookieName=rds-session-v2
\ No newline at end of file
+cookieName=rds-session-v2
+logging.level.root=ERROR
\ No newline at end of file
diff --git a/skill-tree/src/main/resources/application-staging.properties b/skill-tree/src/main/resources/application-staging.properties
index abe06d79..598a2335 100644
--- a/skill-tree/src/main/resources/application-staging.properties
+++ b/skill-tree/src/main/resources/application-staging.properties
@@ -1 +1,2 @@
-cookieName=rds-session-v2-staging
\ No newline at end of file
+cookieName=rds-session-v2-staging
+logging.level.root=WARN
\ No newline at end of file
diff --git a/skill-tree/src/main/resources/application-test.properties b/skill-tree/src/main/resources/application-test.properties
new file mode 100644
index 00000000..55ca50d8
--- /dev/null
+++ b/skill-tree/src/main/resources/application-test.properties
@@ -0,0 +1,5 @@
+cookieName=rds-session-v2-development
+test.db.mysql-image=mysql:8.1.0
+spring.flyway.enabled=true
+spring.flyway.locations=classpath:db/migrations
+logging.level.root=WARN
diff --git a/skill-tree/src/main/resources/logback-test.xml b/skill-tree/src/main/resources/logback-test.xml
deleted file mode 100644
index 3279a689..00000000
--- a/skill-tree/src/main/resources/logback-test.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
- %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n
-
-
-
-
-
-
-
-
-
-
diff --git a/skill-tree/src/test/java/com/RDS/skilltree/TestContainerManager.java b/skill-tree/src/test/java/com/RDS/skilltree/TestContainerManager.java
new file mode 100644
index 00000000..99883b13
--- /dev/null
+++ b/skill-tree/src/test/java/com/RDS/skilltree/TestContainerManager.java
@@ -0,0 +1,21 @@
+package com.RDS.skilltree;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
+import org.springframework.context.annotation.Bean;
+import org.testcontainers.containers.MySQLContainer;
+
+@TestConfiguration
+public class TestContainerManager {
+ @Value("${test.db.mysql-image}")
+ private String MYSQL_IMAGE_NAME;
+
+ @Bean
+ @ServiceConnection
+ public MySQLContainer> mySQLContainer() {
+ MySQLContainer> mysqlContainer = new MySQLContainer<>(MYSQL_IMAGE_NAME);
+ mysqlContainer.start();
+ return mysqlContainer;
+ }
+}
diff --git a/skill-tree/src/test/java/com/RDS/skilltree/integration/skills/CreateEndorsementIntegrationTest.java b/skill-tree/src/test/java/com/RDS/skilltree/integration/skills/CreateEndorsementIntegrationTest.java
new file mode 100644
index 00000000..0f253f49
--- /dev/null
+++ b/skill-tree/src/test/java/com/RDS/skilltree/integration/skills/CreateEndorsementIntegrationTest.java
@@ -0,0 +1,370 @@
+package com.RDS.skilltree.integration.skills;
+
+import static java.util.Objects.requireNonNull;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.when;
+import static utils.TestDataHelper.createUserDetails;
+
+import com.RDS.skilltree.TestContainerManager;
+import com.RDS.skilltree.dtos.CreateEndorsementRequestDto;
+import com.RDS.skilltree.dtos.RdsGetUserDetailsResDto;
+import com.RDS.skilltree.exceptions.EndorsementAlreadyExistsException;
+import com.RDS.skilltree.exceptions.SelfEndorsementNotAllowedException;
+import com.RDS.skilltree.exceptions.SkillNotFoundException;
+import com.RDS.skilltree.models.Endorsement;
+import com.RDS.skilltree.models.Skill;
+import com.RDS.skilltree.models.UserSkills;
+import com.RDS.skilltree.repositories.EndorsementRepository;
+import com.RDS.skilltree.repositories.SkillRepository;
+import com.RDS.skilltree.repositories.UserSkillRepository;
+import com.RDS.skilltree.services.external.RdsService;
+import com.RDS.skilltree.utils.Constants.ExceptionMessages;
+import com.RDS.skilltree.utils.JWTUtils;
+import com.RDS.skilltree.viewmodels.EndorsementViewModel;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Import;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import utils.TestDataHelper;
+import utils.WithCustomMockUser;
+
+@AutoConfigureMockMvc
+@ActiveProfiles("test")
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
+@Import({TestContainerManager.class})
+public class CreateEndorsementIntegrationTest {
+ @Autowired private EndorsementRepository endorsementRepository;
+ @Autowired private SkillRepository skillRepository;
+ @Autowired private UserSkillRepository userSkillRepository;
+
+ @Autowired private MockMvc mockMvc;
+ @Autowired private ObjectMapper objectMapper;
+
+ @MockBean private RdsService rdsService;
+ @MockBean private JWTUtils jwtUtils;
+
+ private final String superUserId = "super-user-id";
+ private final String userId1 = "user-id-1";
+ private final String userId2 = "user-id-2";
+
+ private final String SKILL_NAME = "Spring Boot";
+ private final String ENDORSEMENT_MESSAGE = "Proficient in Spring Boot";
+
+ @BeforeEach
+ void setUp() {
+ skillRepository.deleteAll();
+ endorsementRepository.deleteAll();
+ userSkillRepository.deleteAll();
+
+ RdsGetUserDetailsResDto superUserDetails = createUserDetails(superUserId, true);
+
+ RdsGetUserDetailsResDto user1Details = createUserDetails(userId1, false);
+ RdsGetUserDetailsResDto user2Details = createUserDetails(userId2, false);
+
+ when(rdsService.getUserDetails(superUserId)).thenReturn(superUserDetails);
+ when(rdsService.getUserDetails(userId1)).thenReturn(user1Details);
+ when(rdsService.getUserDetails(userId2)).thenReturn(user2Details);
+ }
+
+ private String createUrl(Integer skillId) {
+ return String.format("/v1/skills/%d/endorsements", skillId);
+ }
+
+ private MvcResult performPostRequest(String url, String requestBody) throws Exception {
+ return mockMvc
+ .perform(
+ MockMvcRequestBuilders.post(url)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(requestBody))
+ .andReturn();
+ }
+
+ private Skill createAndSaveSkill(String skillName) {
+ return skillRepository.save(TestDataHelper.createSkill(skillName, superUserId));
+ }
+
+ private CreateEndorsementRequestDto createEndorsementRequest(String endorseId, String message) {
+ CreateEndorsementRequestDto endorsementRequest = new CreateEndorsementRequestDto();
+ endorsementRequest.setEndorseId(endorseId);
+ endorsementRequest.setMessage(message);
+ return endorsementRequest;
+ }
+
+ private EndorsementViewModel createExpectedEndorsement(
+ Skill skill, String endorseId, String endorserId, String message) {
+ Endorsement endorsement =
+ TestDataHelper.createEndorsement(skill, endorseId, endorserId, message);
+ return TestDataHelper.createEndorsementViewModel(endorsement, rdsService);
+ }
+
+ private EndorsementViewModel extractEndorsementFromResult(MvcResult result) throws Exception {
+ String responseJson = result.getResponse().getContentAsString();
+ return objectMapper.readValue(responseJson, EndorsementViewModel.class);
+ }
+
+ private void assertIsEqual(
+ EndorsementViewModel actualEndorsement, EndorsementViewModel expectedEndorsement) {
+ expectedEndorsement.setId(actualEndorsement.getId());
+ assertThat(actualEndorsement).usingRecursiveComparison().isEqualTo(expectedEndorsement);
+ }
+
+ @Test
+ @DisplayName("Happy flow for superuser - create endorsement for a skill")
+ @WithCustomMockUser(
+ username = superUserId,
+ authorities = {"SUPERUSER"})
+ public void createEndorsement_asSuperUser_shouldCreateEndorsementSuccessfully() throws Exception {
+ Skill skill = createAndSaveSkill(SKILL_NAME);
+ String endorseId = userId1;
+ String endorserId = superUserId;
+ String message = ENDORSEMENT_MESSAGE;
+
+ String requestBody =
+ objectMapper.writeValueAsString(createEndorsementRequest(endorseId, message));
+
+ MvcResult result = performPostRequest(createUrl(skill.getId()), requestBody);
+
+ assertThat(result.getResponse().getStatus()).isEqualTo(201);
+ assertThat(result.getResponse().getContentAsString()).isNotEmpty();
+
+ EndorsementViewModel actualEndorsement = extractEndorsementFromResult(result);
+ EndorsementViewModel expectedEndorsement =
+ createExpectedEndorsement(skill, endorseId, endorserId, message);
+
+ assertIsEqual(actualEndorsement, expectedEndorsement);
+ }
+
+ @Test
+ @DisplayName("Happy flow for regular user - create endorsement for a skill")
+ @WithCustomMockUser(
+ username = userId2,
+ authorities = {"USER"})
+ public void createEndorsement_asRegularUser_shouldCreateEndorsementSuccessfully()
+ throws Exception {
+ Skill skill = createAndSaveSkill(SKILL_NAME);
+ String endorseId = userId1;
+ String endorserId = userId2;
+ String message = ENDORSEMENT_MESSAGE;
+
+ String requestBody =
+ objectMapper.writeValueAsString(createEndorsementRequest(endorseId, message));
+
+ MvcResult result = performPostRequest(createUrl(skill.getId()), requestBody);
+
+ assertThat(result.getResponse().getStatus()).isEqualTo(201);
+ assertThat(result.getResponse().getContentAsString()).isNotEmpty();
+
+ EndorsementViewModel actualEndorsement = extractEndorsementFromResult(result);
+ EndorsementViewModel expectedEndorsement =
+ createExpectedEndorsement(skill, endorseId, endorserId, message);
+
+ assertIsEqual(actualEndorsement, expectedEndorsement);
+ }
+
+ @Test
+ @DisplayName("Error case - self-endorsement not allowed")
+ @WithCustomMockUser(
+ username = userId1,
+ authorities = {"USER"})
+ public void createEndorsement_selfEndorsement_shouldReturnError() throws Exception {
+ Skill skill = createAndSaveSkill(SKILL_NAME);
+ String endorseId = userId1; // self-endorsement
+
+ String requestBody =
+ objectMapper.writeValueAsString(createEndorsementRequest(endorseId, ENDORSEMENT_MESSAGE));
+
+ MvcResult result = performPostRequest(createUrl(skill.getId()), requestBody);
+
+ assertThat(result.getResponse().getStatus()).isEqualTo(405);
+
+ assertThat(result.getResolvedException())
+ .isInstanceOf(SelfEndorsementNotAllowedException.class);
+ assertThat(requireNonNull(result.getResolvedException()).getMessage())
+ .isEqualTo(ExceptionMessages.SELF_ENDORSEMENT_NOT_ALLOWED);
+
+ assertThat(endorsementRepository.count()).isZero();
+ }
+
+ @Test
+ @DisplayName("Error case - non-existent skill ID")
+ @WithCustomMockUser(
+ username = userId2,
+ authorities = {"USER"})
+ public void createEndorsement_nonExistentSkill_shouldReturnError() throws Exception {
+ Integer nonExistentSkillId = 9999;
+
+ String requestBody =
+ objectMapper.writeValueAsString(createEndorsementRequest(userId1, ENDORSEMENT_MESSAGE));
+
+ MvcResult result = performPostRequest(createUrl(nonExistentSkillId), requestBody);
+
+ assertThat(result.getResponse().getStatus()).isEqualTo(404);
+
+ assertThat(result.getResolvedException()).isInstanceOf(SkillNotFoundException.class);
+ assertThat(requireNonNull(result.getResolvedException()).getMessage())
+ .isEqualTo(ExceptionMessages.SKILL_NOT_FOUND);
+
+ assertThat(endorsementRepository.count()).isZero();
+ }
+
+ @Test
+ @DisplayName("Edge case - first endorsement creates UserSkills entry")
+ @WithCustomMockUser(
+ username = superUserId,
+ authorities = {"SUPERUSER"})
+ public void createEndorsement_firstEndorsement_shouldCreateUserSkillsEntry() throws Exception {
+ Skill skill = createAndSaveSkill(SKILL_NAME);
+ String endorseId = userId1;
+ String endorserId = superUserId;
+ String message = ENDORSEMENT_MESSAGE;
+
+ // no user-skills entry exists before the test
+ assertThat(userSkillRepository.findByUserIdAndSkillId(endorseId, skill.getId())).isEmpty();
+
+ String requestBody =
+ objectMapper.writeValueAsString(createEndorsementRequest(endorseId, message));
+ MvcResult result = performPostRequest(createUrl(skill.getId()), requestBody);
+
+ assertThat(result.getResponse().getStatus()).isEqualTo(201);
+
+ // user-skills entry was created
+ assertThat(userSkillRepository.findByUserIdAndSkillId(endorseId, skill.getId())).isNotEmpty();
+
+ EndorsementViewModel actualEndorsement = extractEndorsementFromResult(result);
+ EndorsementViewModel expectedEndorsement =
+ createExpectedEndorsement(skill, endorseId, endorserId, message);
+
+ assertIsEqual(actualEndorsement, expectedEndorsement);
+ }
+
+ @Test
+ @DisplayName("Edge case - endorsement with existing UserSkills entry")
+ @WithCustomMockUser(
+ username = superUserId,
+ authorities = {"SUPERUSER"})
+ public void createEndorsement_withExistingUserSkills_shouldCreateEndorsement() throws Exception {
+ Skill skill = createAndSaveSkill(SKILL_NAME);
+ String endorseId = userId1;
+ String endorserId = superUserId;
+ String message = ENDORSEMENT_MESSAGE;
+
+ // user-skills entry before the test
+ UserSkills userSkill = new UserSkills();
+ userSkill.setUserId(endorseId);
+ userSkill.setSkill(skill);
+ userSkillRepository.save(userSkill);
+
+ String requestBody =
+ objectMapper.writeValueAsString(createEndorsementRequest(endorseId, message));
+ MvcResult result = performPostRequest(createUrl(skill.getId()), requestBody);
+
+ assertThat(result.getResponse().getStatus()).isEqualTo(201);
+
+ EndorsementViewModel actualEndorsement = extractEndorsementFromResult(result);
+ EndorsementViewModel expectedEndorsement =
+ createExpectedEndorsement(skill, endorseId, endorserId, message);
+
+ assertIsEqual(actualEndorsement, expectedEndorsement);
+
+ // no duplicate user-skills entry created
+ assertThat(userSkillRepository.findByUserIdAndSkillId(endorseId, skill.getId())).hasSize(1);
+ }
+
+ @Test
+ @DisplayName("Error case - if endorsement already exists, do not create a duplicate")
+ @WithCustomMockUser(
+ username = userId2,
+ authorities = {"USER"})
+ public void createEndorsement_withExistingEndorsement_shouldNotCreateDuplicateEndorsement()
+ throws Exception {
+ Skill skill = createAndSaveSkill(SKILL_NAME);
+
+ String requestBody =
+ objectMapper.writeValueAsString(createEndorsementRequest(userId1, ENDORSEMENT_MESSAGE));
+
+ // create the first endorsement
+ performPostRequest(createUrl(skill.getId()), requestBody);
+
+ // assert the first endorsement was created
+ assertThat(endorsementRepository.count()).isEqualTo(1);
+
+ // try to create a duplicate endorsement
+ MvcResult result = performPostRequest(createUrl(skill.getId()), requestBody);
+
+ assertThat(result.getResponse().getStatus()).isEqualTo(405);
+ assertThat(result.getResolvedException()).isInstanceOf(EndorsementAlreadyExistsException.class);
+ assertThat(requireNonNull(result.getResolvedException()).getMessage())
+ .isEqualTo(ExceptionMessages.ENDORSEMENT_ALREADY_EXISTS);
+
+ assertThat(endorsementRepository.count()).isEqualTo(1);
+ }
+
+ @Test
+ @DisplayName(
+ "Edge case - if endorsement exists on a different skill, should create endorsement on new skill")
+ @WithCustomMockUser(
+ username = userId2,
+ authorities = {"USER"})
+ public void
+ createEndorsement_withExistingEndorsementOnDiffSkill_shouldCreateEndorsementOnNewSkill()
+ throws Exception {
+ Skill skill1 = createAndSaveSkill(SKILL_NAME);
+ Skill skill2 = createAndSaveSkill("Python");
+
+ String endorseId = userId1;
+ String endorserId = userId2;
+ String message = ENDORSEMENT_MESSAGE;
+
+ String requestBody =
+ objectMapper.writeValueAsString(createEndorsementRequest(userId1, ENDORSEMENT_MESSAGE));
+
+ // create the endorsement for skill1
+ MvcResult result1 = performPostRequest(createUrl(skill1.getId()), requestBody);
+
+ EndorsementViewModel actualEndorsement1 = extractEndorsementFromResult(result1);
+ EndorsementViewModel expectedEndorsement1 =
+ createExpectedEndorsement(skill1, endorseId, endorserId, message);
+
+ assertIsEqual(actualEndorsement1, expectedEndorsement1);
+
+ assertThat(endorsementRepository.count()).isEqualTo(1);
+
+ // create the endorsement for skill2
+ MvcResult result2 = performPostRequest(createUrl(skill2.getId()), requestBody);
+
+ assertThat(result2.getResponse().getStatus()).isEqualTo(201);
+
+ EndorsementViewModel actualEndorsement2 = extractEndorsementFromResult(result2);
+ EndorsementViewModel expectedEndorsement2 =
+ createExpectedEndorsement(skill2, endorseId, endorserId, message);
+
+ assertIsEqual(actualEndorsement2, expectedEndorsement2);
+
+ // assert both endorsements were created
+ assertThat(endorsementRepository.count()).isEqualTo(2);
+ }
+
+ @Test
+ @DisplayName("if user not authenticated, should return 401")
+ public void shouldReturn401_ifUnauthenticatedUser() throws Exception {
+ Skill skill = createAndSaveSkill(SKILL_NAME);
+
+ String endorseId = userId1;
+ String message = ENDORSEMENT_MESSAGE;
+
+ String requestBody =
+ objectMapper.writeValueAsString(createEndorsementRequest(userId1, ENDORSEMENT_MESSAGE));
+
+ MvcResult result = performPostRequest(createUrl(skill.getId()), requestBody);
+ assertThat(result.getResponse().getStatus()).isEqualTo(401);
+ }
+}
diff --git a/skill-tree/src/test/java/com/RDS/skilltree/integration/skills/CreateSkillIntegrationTest.java b/skill-tree/src/test/java/com/RDS/skilltree/integration/skills/CreateSkillIntegrationTest.java
new file mode 100644
index 00000000..de7963c5
--- /dev/null
+++ b/skill-tree/src/test/java/com/RDS/skilltree/integration/skills/CreateSkillIntegrationTest.java
@@ -0,0 +1,160 @@
+package com.RDS.skilltree.integration.skills;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.RDS.skilltree.TestContainerManager;
+import com.RDS.skilltree.dtos.RdsGetUserDetailsResDto;
+import com.RDS.skilltree.enums.SkillTypeEnum;
+import com.RDS.skilltree.models.Skill;
+import com.RDS.skilltree.repositories.SkillRepository;
+import com.RDS.skilltree.services.external.RdsService;
+import com.RDS.skilltree.utils.JWTUtils;
+import com.RDS.skilltree.viewmodels.RdsUserViewModel;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.jsonwebtoken.Claims;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Import;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+import utils.WithCustomMockUser;
+
+@AutoConfigureMockMvc
+@ActiveProfiles("test")
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
+@Import(TestContainerManager.class)
+public class CreateSkillIntegrationTest {
+ @Autowired private MockMvc mockMvc;
+ @Autowired private SkillRepository skillRepository;
+ @MockBean private RdsService rdsService;
+ @MockBean private JWTUtils jwtUtils;
+ @Autowired private ObjectMapper objectMapper;
+
+ private final String baseRoute = "/v1/skills";
+
+ @BeforeEach
+ void setUp() {
+ // Clean up repository
+ skillRepository.deleteAll();
+
+ // Setup super-user detail
+ RdsUserViewModel superUser = new RdsUserViewModel();
+ superUser.setId("super-user-id");
+ RdsUserViewModel.Roles roles = new RdsUserViewModel.Roles();
+ roles.setSuper_user(true);
+ superUser.setRoles(roles);
+
+ RdsGetUserDetailsResDto superUserDetails = new RdsGetUserDetailsResDto();
+ superUserDetails.setUser(superUser);
+
+ // Setup RDS service mock responses
+ when(rdsService.getUserDetails("super-user-id")).thenReturn(superUserDetails);
+
+ // Mock JWTUtils to bypass actual JWT verification
+ Claims mockClaims = mock(Claims.class);
+ when(mockClaims.get("userId", String.class)).thenReturn("super-user-id");
+ when(jwtUtils.validateToken(anyString())).thenReturn(mockClaims);
+ }
+
+ @Test
+ @DisplayName("Happy flow - Super user can create a new skill")
+ @WithCustomMockUser(
+ username = "super-user-id",
+ authorities = {"SUPERUSER"})
+ public void createSkill_validRequest_shouldCreateSkill() throws Exception {
+ String requestBody = "{" + "\"name\": \"Java\"," + "\"type\": \"ATOMIC\"" + "}";
+
+ mockMvc
+ .perform(
+ MockMvcRequestBuilders.post(baseRoute)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(requestBody))
+ .andExpect(MockMvcResultMatchers.status().is(201))
+ .andExpect(MockMvcResultMatchers.jsonPath("$.name").value("Java"))
+ .andExpect(MockMvcResultMatchers.jsonPath("$.type").value("ATOMIC"));
+
+ assert skillRepository.existsByName("Java");
+ }
+
+ @Test
+ @DisplayName("Error case - Cannot create duplicate skill")
+ @WithCustomMockUser(
+ username = "super-user-id",
+ authorities = {"SUPERUSER"})
+ public void createSkill_duplicateName_shouldFail() throws Exception {
+ Skill existingSkill = new Skill();
+ existingSkill.setName("Java");
+ existingSkill.setType(SkillTypeEnum.ATOMIC);
+ existingSkill.setCreatedBy("super-user-id");
+ skillRepository.save(existingSkill);
+
+ String requestBody = "{" + "\"name\": \"Java\"," + "\"type\": \"ATOMIC\"" + "}";
+
+ mockMvc
+ .perform(
+ MockMvcRequestBuilders.post(baseRoute)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(requestBody))
+ .andExpect(MockMvcResultMatchers.status().is(409));
+ }
+
+ @Test
+ @DisplayName("Validation test - Missing required fields")
+ @WithCustomMockUser(
+ username = "super-user-id",
+ authorities = {"SUPERUSER"})
+ public void createSkill_missingRequiredFields_shouldFail() throws Exception {
+ String requestBody = "{" + "\"type\": \"ATOMIC\"" + "}";
+
+ mockMvc
+ .perform(
+ MockMvcRequestBuilders.post(baseRoute)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(requestBody))
+ .andExpect(MockMvcResultMatchers.status().is(400));
+ }
+
+ @Test
+ @DisplayName("Validation test - Invalid skill type")
+ @WithCustomMockUser(
+ username = "super-user-id",
+ authorities = {"SUPERUSER"})
+ public void createSkill_invalidSkillType_shouldFail() throws Exception {
+ String requestBody = "{" + "\"name\": \"Java\"," + "\"type\": \"INVALID_TYPE\"" + "}";
+
+ mockMvc
+ .perform(
+ MockMvcRequestBuilders.post(baseRoute)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(requestBody))
+ .andExpect(MockMvcResultMatchers.status().is(500));
+ }
+
+ @Test
+ @DisplayName("Authorization test - Non-super user cannot create skill")
+ @WithCustomMockUser(
+ username = "normal-user",
+ authorities = {"USER"})
+ public void createSkill_nonSuperUser_shouldFail() throws Exception {
+ String requestBody = "{" + "\"name\": \"Java\"," + "\"type\": \"ATOMIC\"" + "}";
+
+ mockMvc
+ .perform(
+ MockMvcRequestBuilders.post(baseRoute)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(requestBody))
+ .andExpect(MockMvcResultMatchers.status().is(403));
+ }
+}
diff --git a/skill-tree/src/test/java/com/RDS/skilltree/integration/skills/GetAllSkillRequestIntegrationTest.java b/skill-tree/src/test/java/com/RDS/skilltree/integration/skills/GetAllSkillRequestIntegrationTest.java
new file mode 100644
index 00000000..5f13355b
--- /dev/null
+++ b/skill-tree/src/test/java/com/RDS/skilltree/integration/skills/GetAllSkillRequestIntegrationTest.java
@@ -0,0 +1,359 @@
+package com.RDS.skilltree.integration.skills;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.RDS.skilltree.TestContainerManager;
+import com.RDS.skilltree.dtos.RdsGetUserDetailsResDto;
+import com.RDS.skilltree.enums.SkillTypeEnum;
+import com.RDS.skilltree.enums.UserSkillStatusEnum;
+import com.RDS.skilltree.models.Endorsement;
+import com.RDS.skilltree.models.Skill;
+import com.RDS.skilltree.models.UserSkills;
+import com.RDS.skilltree.repositories.EndorsementRepository;
+import com.RDS.skilltree.repositories.SkillRepository;
+import com.RDS.skilltree.repositories.UserSkillRepository;
+import com.RDS.skilltree.services.external.RdsService;
+import com.RDS.skilltree.utils.JWTUtils;
+import com.RDS.skilltree.viewmodels.RdsUserViewModel;
+import io.jsonwebtoken.Claims;
+import jakarta.servlet.http.Cookie;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Import;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+import utils.CustomResultMatchers;
+import utils.WithCustomMockUser;
+
+@AutoConfigureMockMvc
+@ActiveProfiles("test")
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
+@Import(TestContainerManager.class)
+public class GetAllSkillRequestIntegrationTest {
+ @Autowired private MockMvc mockMvc;
+
+ @Autowired private UserSkillRepository userSkillRepository;
+
+ @Autowired private EndorsementRepository endorsementRepository;
+
+ @MockBean private RdsService rdsService;
+
+ @Autowired private SkillRepository skillRepository;
+
+ @MockBean private JWTUtils jwtUtils;
+
+ private final String route = "/v1/skills/requests";
+
+ @BeforeEach
+ void setUp() {
+ // Clean up repositories
+ skillRepository.deleteAll();
+ endorsementRepository.deleteAll();
+ userSkillRepository.deleteAll();
+
+ // Setup super-user detail
+ RdsUserViewModel superUser = new RdsUserViewModel();
+ superUser.setId("super-user-id");
+ RdsUserViewModel.Roles roles = new RdsUserViewModel.Roles();
+ roles.setSuper_user(true);
+ superUser.setRoles(roles);
+
+ RdsGetUserDetailsResDto superUserDetails = new RdsGetUserDetailsResDto();
+ superUserDetails.setUser(superUser);
+
+ // Setup normal-users detail
+ RdsUserViewModel normalUser = new RdsUserViewModel();
+ normalUser.setId("user-id");
+ RdsUserViewModel.Roles normalUserRoles = new RdsUserViewModel.Roles();
+ normalUserRoles.setSuper_user(false);
+ normalUser.setRoles(normalUserRoles);
+
+ RdsGetUserDetailsResDto normalUserDetails = new RdsGetUserDetailsResDto();
+ normalUserDetails.setUser(normalUser);
+
+ RdsUserViewModel normalUser2 = new RdsUserViewModel();
+ normalUser2.setId("user-id-2");
+ normalUser2.setRoles(normalUserRoles);
+ RdsGetUserDetailsResDto normalUser2Details = new RdsGetUserDetailsResDto();
+ normalUser2Details.setUser(normalUser2);
+
+ // Setup mock skills
+ Skill skill1 = new Skill();
+ skill1.setName("Java");
+ skill1.setType(SkillTypeEnum.ATOMIC);
+ skill1.setCreatedBy("super-user-id");
+
+ Skill skill2 = new Skill();
+ skill2.setName("Springboot");
+ skill2.setType(SkillTypeEnum.ATOMIC);
+ skill2.setCreatedBy("super-user-id");
+
+ skillRepository.save(skill1);
+ skillRepository.save(skill2);
+
+ // Setup mock user-skills
+ UserSkills userSkills1 = new UserSkills();
+ userSkills1.setSkill(skill1);
+ userSkills1.setUserId("user-id");
+ userSkills1.setStatus(UserSkillStatusEnum.PENDING);
+
+ UserSkills userSkills2 = new UserSkills();
+ userSkills2.setSkill(skill2);
+ userSkills2.setUserId("user-id");
+ userSkills2.setStatus(UserSkillStatusEnum.APPROVED);
+
+ UserSkills userSkills3 = new UserSkills();
+ userSkills3.setSkill(skill2);
+ userSkills3.setUserId("user-id-2");
+ userSkills3.setStatus(UserSkillStatusEnum.PENDING);
+
+ userSkillRepository.save(userSkills1);
+ userSkillRepository.save(userSkills2);
+ userSkillRepository.save(userSkills3);
+
+ // Setup mock endorsements
+ Endorsement endorsement1 = new Endorsement();
+ endorsement1.setId(1);
+ endorsement1.setEndorserId("super-user-id");
+ endorsement1.setEndorseId("user-id");
+ endorsement1.setSkill(skill1);
+ endorsement1.setMessage("endorsement message");
+
+ Endorsement endorsement2 = new Endorsement();
+ endorsement2.setId(3);
+ endorsement2.setEndorserId("user-id-2");
+ endorsement2.setEndorseId("user-id");
+ endorsement2.setSkill(skill2);
+ endorsement2.setMessage("skill2 for user-id");
+
+ Endorsement endorsement3 = new Endorsement();
+ endorsement3.setId(4);
+ endorsement3.setEndorserId("super-user-id");
+ endorsement3.setEndorseId("user-id-2");
+ endorsement3.setSkill(skill2);
+ endorsement3.setMessage("skill2 for user-id-2");
+
+ endorsementRepository.save(endorsement1);
+ endorsementRepository.save(endorsement2);
+ endorsementRepository.save(endorsement3);
+
+ // Setup RDS service mock responses
+ when(rdsService.getUserDetails("super-user-id")).thenReturn(superUserDetails);
+ when(rdsService.getUserDetails("user-id-2")).thenReturn(normalUser2Details);
+ when(rdsService.getUserDetails("user-id")).thenReturn(normalUserDetails);
+
+ // Mock JWTUtils to bypass actual JWT verification
+ Claims mockClaims = mock(Claims.class);
+ when(mockClaims.get("userId", String.class)).thenReturn("super-user-id");
+ when(jwtUtils.validateToken(anyString())).thenReturn(mockClaims);
+ }
+
+ @Test
+ @DisplayName("Happy flow for SuperUser - should return all requests")
+ @WithCustomMockUser(
+ username = "super-user-id",
+ authorities = {"SUPERUSER"})
+ public void getAllRequests_asSuperUser_shouldReturnAllRequests() throws Exception {
+ mockMvc
+ .perform(MockMvcRequestBuilders.get(route).contentType(MediaType.APPLICATION_JSON))
+ .andExpect(MockMvcResultMatchers.status().is(200))
+ .andExpect(CustomResultMatchers.hasSkillRequest("Java", "user-id", "PENDING"))
+ .andExpect(
+ CustomResultMatchers.hasEndorsement(
+ "Java", "user-id", "super-user-id", "endorsement message"))
+ .andExpect(CustomResultMatchers.hasSkillRequest("Springboot", "user-id", "APPROVED"))
+ .andExpect(
+ CustomResultMatchers.hasEndorsement(
+ "Springboot", "user-id", "user-id-2", "skill2 for user-id"))
+ .andExpect(CustomResultMatchers.hasSkillRequest("Springboot", "user-id-2", "PENDING"))
+ .andExpect(
+ CustomResultMatchers.hasEndorsement(
+ "Springboot", "user-id-2", "super-user-id", "skill2 for user-id-2"))
+ .andExpect(CustomResultMatchers.hasUser("user-id", " "))
+ .andExpect(CustomResultMatchers.hasUser("user-id-2", " "))
+ .andExpect(CustomResultMatchers.hasUser("super-user-id", " "));
+ }
+
+ @Test
+ @DisplayName("Happy flow for normal user - Get all requests where user is endorser")
+ @WithCustomMockUser(
+ username = "user-id-2",
+ authorities = {"USER"})
+ public void getAllRequests_asNormalUser_shouldReturnAllRequestsByEndorser() throws Exception {
+ mockMvc
+ .perform(MockMvcRequestBuilders.get(route).contentType(MediaType.APPLICATION_JSON))
+ .andExpect(MockMvcResultMatchers.status().is(200))
+ .andExpect(CustomResultMatchers.hasSkillRequest("Springboot", "user-id", "APPROVED"))
+ .andExpect(
+ CustomResultMatchers.hasEndorsement(
+ "Springboot", "user-id", "user-id-2", "skill2 for user-id"))
+ .andExpect(CustomResultMatchers.hasUser("user-id", " "))
+ .andExpect(CustomResultMatchers.hasUser("user-id-2", " "));
+ }
+
+ @Test
+ @DisplayName(
+ "Normal user with dev flag - should only see skills they've endorsed with all endorsements")
+ @WithCustomMockUser(
+ username = "user-id-2",
+ authorities = {"USER"})
+ public void getAllRequests_asNormalUserWithDevFlag_shouldOnlySeeSkillsTheyEndorsed()
+ throws Exception {
+ mockMvc
+ .perform(
+ MockMvcRequestBuilders.get(route + "?dev=true").contentType(MediaType.APPLICATION_JSON))
+ .andExpect(MockMvcResultMatchers.status().is(200))
+ .andExpect(CustomResultMatchers.hasSkillRequest("Springboot", "user-id", "APPROVED"))
+ .andExpect(
+ CustomResultMatchers.hasEndorsement(
+ "Springboot", "user-id", "user-id-2", "skill2 for user-id"))
+ .andExpect(CustomResultMatchers.doesNotHaveSkillRequest("Java", "user-id"))
+ .andExpect(CustomResultMatchers.doesNotHaveSkillRequest("Springboot", "user-id-2"))
+ .andExpect(CustomResultMatchers.hasUser("user-id", " "))
+ .andExpect(CustomResultMatchers.hasUser("user-id-2", " "));
+ }
+
+ @Test
+ @DisplayName("Filter requests by status - should return filtered requests")
+ @WithCustomMockUser(
+ username = "super-user-id",
+ authorities = {"SUPERUSER"})
+ public void getAllRequests_ByStatus_ShouldReturnFilteredRequests() throws Exception {
+ mockMvc
+ .perform(
+ MockMvcRequestBuilders.get(route + "?status=APPROVED")
+ .contentType(MediaType.APPLICATION_JSON))
+ .andExpect(MockMvcResultMatchers.status().is(200))
+ .andExpect(CustomResultMatchers.hasSkillRequest("Springboot", "user-id", "APPROVED"))
+ .andExpect(
+ CustomResultMatchers.hasEndorsement(
+ "Springboot", "user-id", "user-id-2", "skill2 for user-id"))
+ .andExpect(CustomResultMatchers.hasUser("user-id", " "))
+ .andExpect(CustomResultMatchers.hasUser("user-id-2", " "));
+ }
+
+ @Test
+ @DisplayName(
+ "Normal user with dev flag filtering by status - should only see endorsed skills with matching status")
+ @WithCustomMockUser(
+ username = "user-id-2",
+ authorities = {"USER"})
+ public void getRequests_asNormalUserByStatusWithDevFlag_shouldOnlyShowEndorsedMatchingRequests()
+ throws Exception {
+ mockMvc
+ .perform(
+ MockMvcRequestBuilders.get(route + "?status=APPROVED&dev=true")
+ .contentType(MediaType.APPLICATION_JSON))
+ .andExpect(MockMvcResultMatchers.status().is(200))
+ .andExpect(CustomResultMatchers.hasSkillRequest("Springboot", "user-id", "APPROVED"))
+ .andExpect(
+ CustomResultMatchers.hasEndorsement(
+ "Springboot", "user-id", "user-id-2", "skill2 for user-id"))
+ .andExpect(CustomResultMatchers.doesNotHaveSkillRequest("Java", "user-id"))
+ .andExpect(CustomResultMatchers.doesNotHaveSkillRequest("Springboot", "user-id-2"))
+ .andExpect(CustomResultMatchers.hasUser("user-id", " "))
+ .andExpect(CustomResultMatchers.hasUser("user-id-2", " "));
+ }
+
+ @Test
+ @DisplayName("If no skill Requests endorsed by user then return empty lists")
+ @WithCustomMockUser(
+ username = "user-id",
+ authorities = {"USER"})
+ public void noSkillRequestsEndorsedByUser_ShouldReturnEmptyLists() throws Exception {
+ mockMvc
+ .perform(MockMvcRequestBuilders.get(route).contentType(MediaType.APPLICATION_JSON))
+ .andExpect(MockMvcResultMatchers.status().is(200))
+ .andExpect(MockMvcResultMatchers.jsonPath("$.requests").isEmpty())
+ .andExpect(MockMvcResultMatchers.jsonPath("$.users").isEmpty());
+ }
+
+ @Test
+ @DisplayName("If no skill Requests endorsed by user with dev flag then return empty lists")
+ @WithCustomMockUser(
+ username = "user-id",
+ authorities = {"USER"})
+ public void noSkillRequestsEndorsedByUser_WithDevFlag_ShouldReturnEmptyLists() throws Exception {
+ mockMvc
+ .perform(
+ MockMvcRequestBuilders.get(route + "?dev=true").contentType(MediaType.APPLICATION_JSON))
+ .andExpect(MockMvcResultMatchers.status().is(200))
+ .andExpect(MockMvcResultMatchers.jsonPath("$.requests").isEmpty())
+ .andExpect(MockMvcResultMatchers.jsonPath("$.users").isEmpty());
+ }
+
+ @Test
+ @DisplayName("If no matching skill requests by status then return empty lists")
+ @WithCustomMockUser(
+ username = "user-id",
+ authorities = {"USER"})
+ public void noMatchingRequestsByStatus_ShouldReturnEmptyLists() throws Exception {
+ mockMvc
+ .perform(
+ MockMvcRequestBuilders.get(route + "?status=REJECTED")
+ .contentType(MediaType.APPLICATION_JSON))
+ .andExpect(MockMvcResultMatchers.status().is(200))
+ .andExpect(MockMvcResultMatchers.jsonPath("$.requests").isEmpty())
+ .andExpect(MockMvcResultMatchers.jsonPath("$.users").isEmpty());
+ }
+
+ @Test
+ @DisplayName("If no matching skill requests by status with dev flag then return empty lists")
+ @WithCustomMockUser(
+ username = "user-id",
+ authorities = {"USER"})
+ public void noMatchingRequestsByStatus_WithDevFlag_ShouldReturnEmptyLists() throws Exception {
+ mockMvc
+ .perform(
+ MockMvcRequestBuilders.get(route + "?status=REJECTED&dev=true")
+ .contentType(MediaType.APPLICATION_JSON))
+ .andExpect(MockMvcResultMatchers.status().is(200))
+ .andExpect(MockMvcResultMatchers.jsonPath("$.requests").isEmpty())
+ .andExpect(MockMvcResultMatchers.jsonPath("$.users").isEmpty());
+ }
+
+ @Test
+ @DisplayName("If no skill requests in DB - return empty lists")
+ @WithCustomMockUser(
+ username = "super-user-id",
+ authorities = {"SUPERUSER"})
+ public void getAllRequests_NoData_ShouldReturnEmptyLists() throws Exception {
+ skillRepository.deleteAll();
+ endorsementRepository.deleteAll();
+ userSkillRepository.deleteAll();
+
+ mockMvc
+ .perform(MockMvcRequestBuilders.get(route).contentType(MediaType.APPLICATION_JSON))
+ .andExpect(MockMvcResultMatchers.status().is(200))
+ .andExpect(MockMvcResultMatchers.jsonPath("$.requests").isEmpty())
+ .andExpect(MockMvcResultMatchers.jsonPath("$.users").isEmpty());
+ }
+
+ @Test
+ @DisplayName("if invalid cookie, return 401")
+ public void ifInvalidCoolie_ShouldReturnUnauthorized() throws Exception {
+ Cookie authCookie =
+ new Cookie(
+ "cookie",
+ "eyJhbGciOiJSUzI1NiIsInR5cCI.eyJ1c2VySWQiOiI2N2lSeXJOTWQ.E-EtcPOj7Ca5l8JuE0hwky0rRikYSNZBvC");
+
+ mockMvc
+ .perform(
+ MockMvcRequestBuilders.get(route)
+ .cookie(authCookie)
+ .contentType(MediaType.APPLICATION_JSON))
+ .andExpect(MockMvcResultMatchers.status().is(401));
+ }
+}
diff --git a/skill-tree/src/test/java/com/RDS/skilltree/integration/skills/GetAllSkillsIntegrationTest.java b/skill-tree/src/test/java/com/RDS/skilltree/integration/skills/GetAllSkillsIntegrationTest.java
new file mode 100644
index 00000000..2c9bfa30
--- /dev/null
+++ b/skill-tree/src/test/java/com/RDS/skilltree/integration/skills/GetAllSkillsIntegrationTest.java
@@ -0,0 +1,112 @@
+package com.RDS.skilltree.integration.skills;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.RDS.skilltree.TestContainerManager;
+import com.RDS.skilltree.enums.SkillTypeEnum;
+import com.RDS.skilltree.models.Skill;
+import com.RDS.skilltree.repositories.SkillRepository;
+import com.RDS.skilltree.services.SkillService;
+import com.RDS.skilltree.services.external.RdsService;
+import com.RDS.skilltree.utils.JWTUtils;
+import io.jsonwebtoken.Claims;
+import jakarta.servlet.http.Cookie;
+import java.util.Arrays;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Import;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+import utils.WithCustomMockUser;
+
+@AutoConfigureMockMvc
+@ActiveProfiles("test")
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
+@Import(TestContainerManager.class)
+public class GetAllSkillsIntegrationTest {
+
+ @Autowired private SkillService skillService;
+
+ @Autowired private SkillRepository skillRepository;
+
+ @Autowired private MockMvc mockMvc;
+ @MockBean private JWTUtils jwtUtils;
+
+ @MockBean private RdsService rdsService;
+
+ private final String route = "/v1/skills";
+
+ @BeforeEach
+ public void setUp() {
+ skillRepository.deleteAll();
+ Skill skill1 = new Skill();
+ skill1.setName("Java");
+ skill1.setType(SkillTypeEnum.ATOMIC);
+ skill1.setCreatedBy("s9zQUm4XbVEz7xzRkaZv");
+
+ Skill skill2 = new Skill();
+ skill2.setName("Springboot");
+ skill2.setType(SkillTypeEnum.ATOMIC);
+ skill2.setCreatedBy("s9zQUm4XbVEz7xzRkaZv");
+
+ skillRepository.saveAll(Arrays.asList(skill1, skill2));
+
+ // Mock JWTUtils to bypass actual JWT verification
+ Claims mockClaims = mock(Claims.class);
+ when(mockClaims.get("userId", String.class)).thenReturn("rds-user");
+ when(jwtUtils.validateToken(anyString())).thenReturn(mockClaims);
+ }
+
+ @Test
+ @WithCustomMockUser(
+ username = "rds-user",
+ authorities = {"SUPERUSER"})
+ @DisplayName("happy flow - returns all skills that are in db")
+ public void getAllSkillsHappyFlow() throws Exception {
+ mockMvc
+ .perform(MockMvcRequestBuilders.get(route).accept(MediaType.APPLICATION_JSON))
+ .andExpect(MockMvcResultMatchers.status().is(200))
+ .andExpect(MockMvcResultMatchers.jsonPath("$[0].name").value("Java"))
+ .andExpect(MockMvcResultMatchers.jsonPath("$[1].name").value("Springboot"));
+ }
+
+ @Test
+ @DisplayName("if no skills available, return empty list")
+ @WithCustomMockUser(
+ username = "rds-user",
+ authorities = {"SUPERUSER"})
+ public void noSkillsAvailable_shouldReturnEmptyList() throws Exception {
+ skillRepository.deleteAll();
+
+ mockMvc
+ .perform(MockMvcRequestBuilders.get(route).accept(MediaType.APPLICATION_JSON))
+ .andExpect(MockMvcResultMatchers.status().is(200))
+ .andExpect(MockMvcResultMatchers.jsonPath("$").isEmpty());
+ }
+
+ @Test
+ @DisplayName("if invalid cookie, return 401")
+ public void ifInvalidCoolie_returnUnauthorized() throws Exception {
+ Cookie authCookie =
+ new Cookie(
+ "cookie",
+ "eyJhbGciOiJSUzI1NiIsInR5cCI.eyJ1c2VySWQiOiI2N2lSeXJOTWQ.E-EtcPOj7Ca5l8JuE0hwky0rRikYSNZBvC");
+
+ mockMvc
+ .perform(
+ MockMvcRequestBuilders.get(route).cookie(authCookie).accept(MediaType.APPLICATION_JSON))
+ .andExpect(MockMvcResultMatchers.status().is(401));
+ }
+}
diff --git a/skill-tree/src/test/java/com/RDS/skilltree/integration/skills/GetEndorsementsBySkillIdIntegrationTest.java b/skill-tree/src/test/java/com/RDS/skilltree/integration/skills/GetEndorsementsBySkillIdIntegrationTest.java
new file mode 100644
index 00000000..f91046c9
--- /dev/null
+++ b/skill-tree/src/test/java/com/RDS/skilltree/integration/skills/GetEndorsementsBySkillIdIntegrationTest.java
@@ -0,0 +1,238 @@
+package com.RDS.skilltree.integration.skills;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static utils.TestDataHelper.*;
+
+import com.RDS.skilltree.TestContainerManager;
+import com.RDS.skilltree.dtos.RdsGetUserDetailsResDto;
+import com.RDS.skilltree.models.Endorsement;
+import com.RDS.skilltree.models.Skill;
+import com.RDS.skilltree.repositories.EndorsementRepository;
+import com.RDS.skilltree.repositories.SkillRepository;
+import com.RDS.skilltree.services.external.RdsService;
+import com.RDS.skilltree.utils.JWTUtils;
+import com.RDS.skilltree.viewmodels.EndorsementViewModel;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.jsonwebtoken.Claims;
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Import;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import utils.TestDataHelper;
+import utils.WithCustomMockUser;
+
+@AutoConfigureMockMvc
+@ActiveProfiles("test")
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
+@Import({TestContainerManager.class})
+public class GetEndorsementsBySkillIdIntegrationTest {
+ @Autowired private EndorsementRepository endorsementRepository;
+ @Autowired private SkillRepository skillRepository;
+
+ @Autowired private MockMvc mockMvc;
+ @Autowired private ObjectMapper objectMapper;
+
+ @MockBean private RdsService rdsService;
+ @MockBean private JWTUtils jwtUtils;
+
+ private final String superUserId = "super-user-id";
+ private final String userId1 = "user-id-1";
+ private final String userId2 = "user-id-2";
+
+ @BeforeEach
+ void setUp() {
+ skillRepository.deleteAll();
+ endorsementRepository.deleteAll();
+
+ RdsGetUserDetailsResDto superUserDetails = createUserDetails(superUserId, true);
+
+ RdsGetUserDetailsResDto user1Details = createUserDetails(userId1, false);
+ RdsGetUserDetailsResDto user2Details = createUserDetails(userId2, false);
+
+ when(rdsService.getUserDetails(superUserId)).thenReturn(superUserDetails);
+ when(rdsService.getUserDetails(userId1)).thenReturn(user1Details);
+ when(rdsService.getUserDetails(userId2)).thenReturn(user2Details);
+
+ Claims mockClaims = mock(Claims.class);
+ when(jwtUtils.validateToken(anyString())).thenReturn(mockClaims);
+ }
+
+ private String createUrl(Integer skillId) {
+ return "/v1/skills/" + skillId + "/endorsements";
+ }
+
+ private Skill createAndSaveSkill(String skillName) {
+ return skillRepository.save(TestDataHelper.createSkill(skillName, superUserId));
+ }
+
+ private Endorsement createAndSaveEndorsement(
+ Skill skill, String endorseId, String endorserId, String message) {
+ Endorsement endorsement =
+ TestDataHelper.createEndorsement(skill, endorseId, endorserId, message);
+ return endorsementRepository.save(endorsement);
+ }
+
+ private EndorsementViewModel createEndorsementViewModel(Endorsement endorsement) {
+ return TestDataHelper.createEndorsementViewModel(endorsement, rdsService);
+ }
+
+ private List extractEndorsementsFromResult(MvcResult result)
+ throws UnsupportedEncodingException, JsonProcessingException {
+ String responseJson = result.getResponse().getContentAsString();
+ return objectMapper.readValue(responseJson, new TypeReference>() {});
+ }
+
+ private MvcResult performGetRequest(String url) throws Exception {
+ return mockMvc
+ .perform(MockMvcRequestBuilders.get(url).contentType(MediaType.APPLICATION_JSON))
+ .andReturn();
+ }
+
+ @Test
+ @DisplayName("Get endorsements for a skill with multiple endorsements")
+ @WithCustomMockUser(
+ username = superUserId,
+ authorities = {"SUPERUSER"})
+ public void getEndorsements_forSkillWithMultipleEndorsements_shouldReturnAllEndorsements()
+ throws Exception {
+ Skill skill = createAndSaveSkill("Java");
+
+ String endorseId = userId1;
+ String endorserId1 = userId2;
+ String endorserId2 = superUserId;
+ String endorsementMessage1 = "Good Java knowledge";
+ String endorsementMessage2 = "Excellent Java skills";
+
+ Endorsement endorsement1 =
+ createAndSaveEndorsement(skill, endorseId, endorserId1, endorsementMessage1);
+ Endorsement endorsement2 =
+ createAndSaveEndorsement(skill, endorseId, endorserId2, endorsementMessage2);
+
+ MvcResult result = performGetRequest(createUrl(skill.getId()));
+
+ assertThat(result.getResponse().getStatus()).isEqualTo(200);
+
+ List actualEndorsements = extractEndorsementsFromResult(result);
+ List expectedEndorsements =
+ Arrays.asList(
+ createEndorsementViewModel(endorsement1), createEndorsementViewModel(endorsement2));
+
+ assertThat(actualEndorsements).hasSize(expectedEndorsements.size());
+ assertThat(actualEndorsements).usingRecursiveComparison().isEqualTo(expectedEndorsements);
+ }
+
+ @Test
+ @DisplayName("Get endorsements for a skill with single endorsement")
+ @WithCustomMockUser(
+ username = superUserId,
+ authorities = {"SUPERUSER"})
+ public void getEndorsements_forSkillWithSingleEndorsement_shouldReturnOneEndorsement()
+ throws Exception {
+ Skill skill = createAndSaveSkill("Python");
+
+ String endorseId = userId2;
+ String endorserId = superUserId;
+ String endorsementMessage = "Good Python knowledge";
+
+ Endorsement endorsement =
+ createAndSaveEndorsement(skill, endorseId, endorserId, endorsementMessage);
+
+ MvcResult result = performGetRequest(createUrl(skill.getId()));
+
+ assertThat(result.getResponse().getStatus()).isEqualTo(200);
+
+ List actualEndorsements = extractEndorsementsFromResult(result);
+ EndorsementViewModel expectedEndorsements = createEndorsementViewModel(endorsement);
+
+ assertThat(actualEndorsements).hasSize(1);
+ assertThat(actualEndorsements.get(0))
+ .usingRecursiveComparison()
+ .isEqualTo(expectedEndorsements);
+ }
+
+ @Test
+ @DisplayName("Get endorsements for a skill with no endorsements")
+ @WithCustomMockUser(
+ username = superUserId,
+ authorities = {"SUPERUSER"})
+ public void getEndorsements_forSkillWithNoEndorsements_shouldReturnEmptyList() throws Exception {
+ Skill skill = createAndSaveSkill("JavaScript");
+
+ MvcResult result = performGetRequest(createUrl(skill.getId()));
+
+ assertThat(result.getResponse().getStatus()).isEqualTo(200);
+
+ List actualEndorsements = extractEndorsementsFromResult(result);
+ assertThat(actualEndorsements).isEmpty();
+ }
+
+ @Test
+ @DisplayName("Get endorsements for non-existent skill ID")
+ @WithCustomMockUser(
+ username = superUserId,
+ authorities = {"SUPERUSER"})
+ public void getEndorsements_forNonExistentSkillId_shouldReturnEmptyList() throws Exception {
+ Integer nonExistentSkillId = 999;
+
+ MvcResult result = performGetRequest(createUrl(nonExistentSkillId));
+ assertThat(result.getResponse().getStatus()).isEqualTo(200);
+
+ List actualEndorsements = extractEndorsementsFromResult(result);
+ assertThat(actualEndorsements).isEmpty();
+ }
+
+ @Test
+ @DisplayName("non super-user can access endorsements endpoint")
+ @WithCustomMockUser(
+ username = userId1,
+ authorities = {"USER"})
+ public void normalUser_canAccessEndorsements() throws Exception {
+ Skill skill = createAndSaveSkill("Java");
+ String endorseId = userId1;
+ String endorserId = superUserId;
+ String endorsementMessage = "Good Java knowledge";
+
+ Endorsement endorsement =
+ createAndSaveEndorsement(skill, endorseId, endorserId, endorsementMessage);
+
+ MvcResult result = performGetRequest(createUrl(skill.getId()));
+
+ assertThat(result.getResponse().getStatus()).isEqualTo(200);
+
+ List actualEndorsements = extractEndorsementsFromResult(result);
+ EndorsementViewModel expectedEndorsements = createEndorsementViewModel(endorsement);
+
+ assertThat(actualEndorsements).hasSize(1);
+ assertThat(actualEndorsements.get(0))
+ .usingRecursiveComparison()
+ .isEqualTo(expectedEndorsements);
+ }
+
+ @Test
+ @DisplayName("Unauthenticated request returns 401")
+ public void unauthenticatedRequest_returnsUnauthorized() throws Exception {
+ Skill skill = createAndSaveSkill("Java");
+
+ MvcResult result = performGetRequest(createUrl(skill.getId()));
+ assertThat(result.getResponse().getStatus()).isEqualTo(401);
+ }
+}
diff --git a/skill-tree/src/test/java/com/RDS/skilltree/integration/skills/SkillRequestActionIntegrationTest.java b/skill-tree/src/test/java/com/RDS/skilltree/integration/skills/SkillRequestActionIntegrationTest.java
new file mode 100644
index 00000000..8e406c0e
--- /dev/null
+++ b/skill-tree/src/test/java/com/RDS/skilltree/integration/skills/SkillRequestActionIntegrationTest.java
@@ -0,0 +1,210 @@
+package com.RDS.skilltree.integration.skills;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.RDS.skilltree.TestContainerManager;
+import com.RDS.skilltree.dtos.RdsGetUserDetailsResDto;
+import com.RDS.skilltree.dtos.SkillRequestActionRequestDto;
+import com.RDS.skilltree.enums.SkillTypeEnum;
+import com.RDS.skilltree.enums.UserSkillStatusEnum;
+import com.RDS.skilltree.models.Skill;
+import com.RDS.skilltree.models.UserSkills;
+import com.RDS.skilltree.repositories.SkillRepository;
+import com.RDS.skilltree.repositories.UserSkillRepository;
+import com.RDS.skilltree.services.external.RdsService;
+import com.RDS.skilltree.utils.JWTUtils;
+import com.RDS.skilltree.viewmodels.RdsUserViewModel;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.jsonwebtoken.Claims;
+import org.junit.jupiter.api.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Import;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+import utils.WithCustomMockUser;
+
+@AutoConfigureMockMvc
+@ActiveProfiles("test")
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
+@Import(TestContainerManager.class)
+public class SkillRequestActionIntegrationTest {
+ @Autowired private MockMvc mockMvc;
+ @Autowired private UserSkillRepository userSkillRepository;
+ @Autowired private SkillRepository skillRepository;
+ @MockBean private RdsService rdsService;
+ @MockBean private JWTUtils jwtUtils;
+ @Autowired private ObjectMapper objectMapper;
+ private Skill skill;
+ private final String baseRoute = "/v1/skills/requests";
+
+ @BeforeEach
+ void setUp() {
+ // Clean up repositories
+ skillRepository.deleteAll();
+ userSkillRepository.deleteAll();
+
+ // Setup super-user detail
+ RdsUserViewModel superUser = new RdsUserViewModel();
+ superUser.setId("super-user-id");
+ RdsUserViewModel.Roles roles = new RdsUserViewModel.Roles();
+ roles.setSuper_user(true);
+ superUser.setRoles(roles);
+
+ RdsGetUserDetailsResDto superUserDetails = new RdsGetUserDetailsResDto();
+ superUserDetails.setUser(superUser);
+
+ // Setup mock skill
+ skill = new Skill();
+ skill.setName("Java");
+ skill.setType(SkillTypeEnum.ATOMIC);
+ skill.setCreatedBy("super-user-id");
+ skill = skillRepository.save(skill);
+
+ // Setup mock user-skill
+ UserSkills userSkill = new UserSkills();
+ userSkill.setSkill(skill);
+ userSkill.setUserId("test-user-id");
+ userSkill.setStatus(UserSkillStatusEnum.PENDING);
+ userSkillRepository.save(userSkill);
+
+ // Setup RDS service mock responses
+ when(rdsService.getUserDetails("super-user-id")).thenReturn(superUserDetails);
+
+ // Mock JWTUtils to bypass actual JWT verification
+ Claims mockClaims = mock(Claims.class);
+ when(mockClaims.get("userId", String.class)).thenReturn("super-user-id");
+ when(jwtUtils.validateToken(anyString())).thenReturn(mockClaims);
+ }
+
+ @Test
+ @DisplayName("Happy flow - Super user can approve a skill request")
+ @WithCustomMockUser(
+ username = "super-user-id",
+ authorities = {"SUPERUSER"})
+ public void approveSkillRequest_validRequest_shouldApproveSkillRequest() throws Exception {
+ SkillRequestActionRequestDto requestDto = new SkillRequestActionRequestDto();
+ requestDto.setEndorseId("test-user-id");
+ requestDto.setAction(UserSkillStatusEnum.APPROVED);
+ String requestBody = objectMapper.writeValueAsString(requestDto);
+
+ mockMvc
+ .perform(
+ MockMvcRequestBuilders.post(baseRoute + "/" + skill.getId() + "/action")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(requestBody))
+ .andExpect(MockMvcResultMatchers.status().is(200))
+ .andExpect(MockMvcResultMatchers.jsonPath("$.message").value("approved"));
+
+ // Verify the status was updated in database
+ UserSkills updatedUserSkill =
+ userSkillRepository.findByUserIdAndSkillId("test-user-id", skill.getId()).get(0);
+ Assertions.assertEquals(UserSkillStatusEnum.APPROVED, updatedUserSkill.getStatus());
+ }
+
+ @Test
+ @DisplayName("Happy flow - Super user can reject a skill request")
+ @WithCustomMockUser(
+ username = "super-user-id",
+ authorities = {"SUPERUSER"})
+ public void rejectSkillRequest_validRequest_shouldRejectSkillRequest() throws Exception {
+ SkillRequestActionRequestDto requestDto = new SkillRequestActionRequestDto();
+ requestDto.setEndorseId("test-user-id");
+ requestDto.setAction(UserSkillStatusEnum.REJECTED);
+ String requestBody = objectMapper.writeValueAsString(requestDto);
+
+ mockMvc
+ .perform(
+ MockMvcRequestBuilders.post(baseRoute + "/" + skill.getId() + "/action")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(requestBody))
+ .andExpect(MockMvcResultMatchers.status().is(200))
+ .andExpect(MockMvcResultMatchers.jsonPath("$.message").value("rejected"));
+
+ // Verify the status was updated in database
+ UserSkills updatedUserSkill =
+ userSkillRepository.findByUserIdAndSkillId("test-user-id", skill.getId()).get(0);
+ Assertions.assertEquals(UserSkillStatusEnum.REJECTED, updatedUserSkill.getStatus());
+ }
+
+ @Test
+ @DisplayName("Error case - Request with non-existent skill ID")
+ @WithCustomMockUser(
+ username = "super-user-id",
+ authorities = {"SUPERUSER"})
+ public void approveSkillRequest_NonExistentSkillId_ShouldFail() throws Exception {
+ SkillRequestActionRequestDto requestDto = new SkillRequestActionRequestDto();
+ requestDto.setEndorseId("test-user-id");
+ requestDto.setAction(UserSkillStatusEnum.APPROVED);
+ String requestBody = objectMapper.writeValueAsString(requestDto);
+
+ mockMvc
+ .perform(
+ MockMvcRequestBuilders.post(baseRoute + "/123/action")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(requestBody))
+ .andExpect(MockMvcResultMatchers.status().is(404));
+ }
+
+ @Test
+ @DisplayName("Error case - Request with non-existent user ID")
+ @WithCustomMockUser(
+ username = "super-user-id",
+ authorities = {"SUPERUSER"})
+ public void approveSkillRequest_NonExistentUserId_ShouldFail() throws Exception {
+ SkillRequestActionRequestDto requestDto = new SkillRequestActionRequestDto();
+ requestDto.setEndorseId("non-existent-user");
+ requestDto.setAction(UserSkillStatusEnum.APPROVED);
+ String requestBody = objectMapper.writeValueAsString(requestDto);
+
+ mockMvc
+ .perform(
+ MockMvcRequestBuilders.post(baseRoute + "/" + skill.getId() + "/action")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(requestBody))
+ .andExpect(MockMvcResultMatchers.status().is(404));
+ }
+
+ @Test
+ @DisplayName("Validation test - Missing required fields")
+ @WithCustomMockUser(
+ username = "super-user-id",
+ authorities = {"SUPERUSER"})
+ public void approveSkillRequest_MissingRequiredFields_ShouldFail() throws Exception {
+ String requestBody = "{}";
+
+ mockMvc
+ .perform(
+ MockMvcRequestBuilders.post(baseRoute + "/" + skill.getId() + "/action")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(requestBody))
+ .andExpect(MockMvcResultMatchers.status().is(400));
+ }
+
+ @Test
+ @DisplayName("Authorization test - Non-super user cannot access endpoint")
+ @WithCustomMockUser(
+ username = "normal-user",
+ authorities = {"USER"})
+ public void approveSkillRequest_NonSuperUser_ShouldFail() throws Exception {
+ SkillRequestActionRequestDto requestDto = new SkillRequestActionRequestDto();
+ requestDto.setEndorseId("test-user-id");
+ requestDto.setAction(UserSkillStatusEnum.APPROVED);
+ String requestBody = objectMapper.writeValueAsString(requestDto);
+
+ mockMvc
+ .perform(
+ MockMvcRequestBuilders.post(baseRoute + "/" + skill.getId() + "/action")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(requestBody))
+ .andExpect(MockMvcResultMatchers.status().is(403));
+ }
+}
diff --git a/skill-tree/src/test/java/com/RDS/skilltree/utils/UUIDValidationInterceptorTest.java b/skill-tree/src/test/java/com/RDS/skilltree/unit/utils/UUIDValidationInterceptorTest.java
similarity index 92%
rename from skill-tree/src/test/java/com/RDS/skilltree/utils/UUIDValidationInterceptorTest.java
rename to skill-tree/src/test/java/com/RDS/skilltree/unit/utils/UUIDValidationInterceptorTest.java
index f17e6cc1..b0d431da 100644
--- a/skill-tree/src/test/java/com/RDS/skilltree/utils/UUIDValidationInterceptorTest.java
+++ b/skill-tree/src/test/java/com/RDS/skilltree/unit/utils/UUIDValidationInterceptorTest.java
@@ -1,14 +1,14 @@
-package com.RDS.skilltree.utils;
+package com.RDS.skilltree.unit.utils;
+import static org.junit.Assert.assertTrue;
import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.when;
import com.RDS.skilltree.exceptions.InvalidParameterException;
+import com.RDS.skilltree.utils.UUIDValidationInterceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.UUID;
-import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
@@ -25,7 +25,6 @@ class UUIDValidationInterceptorTest {
@InjectMocks private UUIDValidationInterceptor interceptor;
@Test
- @Disabled
public void itShouldReturnTrueIfValidUserIDIsGiven() {
when(request.getParameter("skillID")).thenReturn(null);
when(request.getParameter("userID")).thenReturn(UUID.randomUUID().toString());
@@ -34,7 +33,6 @@ public void itShouldReturnTrueIfValidUserIDIsGiven() {
}
@Test
- @Disabled
public void itShouldReturnTrueIfValidSkillIDIsGiven() {
when(request.getParameter("userID")).thenReturn(null);
when(request.getParameter("skillID")).thenReturn(UUID.randomUUID().toString());
@@ -43,7 +41,6 @@ public void itShouldReturnTrueIfValidSkillIDIsGiven() {
}
@Test
- @Disabled
public void itShouldReturnTrueIfValidUserIDAndValidSkillIDIsGiven() {
when(request.getParameter("userID")).thenReturn(UUID.randomUUID().toString());
when(request.getParameter("skillID")).thenReturn(UUID.randomUUID().toString());
@@ -52,7 +49,6 @@ public void itShouldReturnTrueIfValidUserIDAndValidSkillIDIsGiven() {
}
@Test
- @Disabled
public void itShouldReturnFalseIfInvalidUserIDIsGiven() {
when(request.getParameter("userID")).thenReturn("null");
when(request.getParameter("skillID")).thenReturn(UUID.randomUUID().toString());
@@ -62,7 +58,6 @@ public void itShouldReturnFalseIfInvalidUserIDIsGiven() {
}
@Test
- @Disabled
public void itShouldReturnFalseIfInvalidSkillIDIsGiven() {
when(request.getParameter("userID")).thenReturn(UUID.randomUUID().toString());
when(request.getParameter("skillID")).thenReturn("null");
@@ -72,7 +67,6 @@ public void itShouldReturnFalseIfInvalidSkillIDIsGiven() {
}
@Test
- @Disabled
public void itShouldReturnFalseIfInvalidUserIDAndInvalidSkillIDIsGiven() {
when(request.getParameter("userID")).thenReturn("invalid-user-id");
when(request.getParameter("skillID")).thenReturn("invalid-skill-id");
diff --git a/skill-tree/src/test/java/utils/CustomResultMatchers.java b/skill-tree/src/test/java/utils/CustomResultMatchers.java
new file mode 100644
index 00000000..2c354a94
--- /dev/null
+++ b/skill-tree/src/test/java/utils/CustomResultMatchers.java
@@ -0,0 +1,119 @@
+package utils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.test.web.servlet.ResultMatcher;
+
+public class CustomResultMatchers {
+
+ public static ResultMatcher hasSkillRequest(String skillName, String endorseId, String status) {
+ return result -> {
+ String json = result.getResponse().getContentAsString();
+ ObjectMapper objectMapper = new ObjectMapper();
+ JsonNode root = objectMapper.readTree(json);
+
+ // Find the request for the given skillName and endorseId
+ JsonNode matchingSkillRequest = findMatchingSkillRequest(root, skillName, endorseId);
+ assertThat(matchingSkillRequest).isNotNull().isNotEmpty();
+
+ assertThat(matchingSkillRequest.get("endorseId").asText()).isEqualTo(endorseId);
+ assertThat(matchingSkillRequest.get("status").asText()).isEqualTo(status);
+ };
+ }
+
+ public static ResultMatcher hasEndorsement(
+ String skillName, String endorseId, String endorserId, String message) {
+ return result -> {
+ String json = result.getResponse().getContentAsString();
+ ObjectMapper objectMapper = new ObjectMapper();
+ JsonNode root = objectMapper.readTree(json);
+
+ // Find the request for the given skillName and endorseId
+ JsonNode matchingSkillRequest = findMatchingSkillRequest(root, skillName, endorseId);
+ assertThat(matchingSkillRequest).isNotNull().isNotEmpty();
+
+ // Check endorsements
+ JsonNode endorsements = matchingSkillRequest.get("endorsements");
+ assertThat(endorsements).isNotNull().isNotEmpty();
+
+ // Find matching endorsement by endorserId
+ JsonNode matchingEndorsement = findByField(endorsements, "endorserId", endorserId);
+ assertThat(matchingEndorsement).isNotNull();
+
+ // Assert endorsement details
+ assertThat(matchingEndorsement.get("endorserId").asText()).isEqualTo(endorserId);
+ assertThat(matchingEndorsement.get("message").asText()).isEqualTo(message);
+ };
+ }
+
+ public static ResultMatcher hasUser(String userId, String name) {
+ return result -> {
+ String json = result.getResponse().getContentAsString();
+ ObjectMapper objectMapper = new ObjectMapper();
+ JsonNode root = objectMapper.readTree(json);
+
+ JsonNode users = root.get("users");
+ assertThat(users).isNotNull().isNotEmpty();
+
+ // Find user by userId
+ JsonNode matchingUser = findByField(users, "id", userId);
+ assertThat(matchingUser).isNotNull();
+
+ // Assert user details
+ assertThat(matchingUser.get("id").asText()).isEqualTo(userId);
+ assertThat(matchingUser.get("name").asText()).isEqualTo(name);
+ };
+ }
+
+ public static ResultMatcher doesNotHaveSkillRequest(String skillName, String endorseId) {
+ return result -> {
+ String json = result.getResponse().getContentAsString();
+ ObjectMapper objectMapper = new ObjectMapper();
+ JsonNode root = objectMapper.readTree(json);
+
+ JsonNode requests = root.get("requests");
+ if (requests == null || requests.isEmpty()) {
+ // If requests is null or empty, the test passes as the request definitely doesn't exist
+ return;
+ }
+
+ // Find the request for the given skillName and endorseId
+ JsonNode matchingRequest = findBySkillAndEndorseId(requests, skillName, endorseId);
+
+ // Assert that the request is null
+ assertThat(matchingRequest).isNull();
+ };
+ }
+
+ private static JsonNode findByField(JsonNode array, String fieldName, String value) {
+ for (JsonNode node : array) {
+ if (node.has(fieldName) && node.get(fieldName).asText().equals(value)) {
+ return node;
+ }
+ }
+ return null;
+ }
+
+ private static JsonNode findMatchingSkillRequest(
+ JsonNode root, String skillName, String endorseId) {
+ JsonNode requests = root.get("requests");
+ assertThat(requests).isNotNull().isNotEmpty();
+
+ return findBySkillAndEndorseId(requests, skillName, endorseId);
+ }
+
+ private static JsonNode findBySkillAndEndorseId(
+ JsonNode array, String skillName, String endorseId) {
+ for (JsonNode node : array) {
+ if (node.has("skillName")
+ && node.get("skillName").asText().equals(skillName)
+ && node.has("endorseId")
+ && node.get("endorseId").asText().equals(endorseId)) {
+ return node;
+ }
+ }
+ return null;
+ }
+}
diff --git a/skill-tree/src/test/java/utils/CustomSecurityContextFactory.java b/skill-tree/src/test/java/utils/CustomSecurityContextFactory.java
new file mode 100644
index 00000000..bcb98cac
--- /dev/null
+++ b/skill-tree/src/test/java/utils/CustomSecurityContextFactory.java
@@ -0,0 +1,39 @@
+package utils;
+
+import com.RDS.skilltree.enums.UserRoleEnum;
+import com.RDS.skilltree.models.JwtUser;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.test.context.support.WithSecurityContextFactory;
+
+public class CustomSecurityContextFactory
+ implements WithSecurityContextFactory {
+ @Override
+ public SecurityContext createSecurityContext(WithCustomMockUser customMockUser) {
+ SecurityContext context = SecurityContextHolder.createEmptyContext();
+
+ List roles =
+ Arrays.stream(customMockUser.authorities()).map(UserRoleEnum::valueOf).toList();
+ UserRoleEnum mainRole = roles.isEmpty() ? UserRoleEnum.USER : roles.get(0);
+
+ // Create jwt user
+ JwtUser jwtUser = new JwtUser(customMockUser.username(), mainRole);
+
+ // Map roles to Spring Security authorities
+ List authorities =
+ roles.stream()
+ .map(role -> new SimpleGrantedAuthority(role.name()))
+ .collect(Collectors.toList());
+
+ // Set JwtUser as the principal in Authentication
+ Authentication auth = new UsernamePasswordAuthenticationToken(jwtUser, null, authorities);
+ context.setAuthentication(auth);
+ return context;
+ }
+}
diff --git a/skill-tree/src/test/java/utils/TestDataHelper.java b/skill-tree/src/test/java/utils/TestDataHelper.java
new file mode 100644
index 00000000..5daab5ac
--- /dev/null
+++ b/skill-tree/src/test/java/utils/TestDataHelper.java
@@ -0,0 +1,64 @@
+package utils;
+
+import com.RDS.skilltree.dtos.RdsGetUserDetailsResDto;
+import com.RDS.skilltree.enums.SkillTypeEnum;
+import com.RDS.skilltree.models.Endorsement;
+import com.RDS.skilltree.models.Skill;
+import com.RDS.skilltree.services.external.RdsService;
+import com.RDS.skilltree.viewmodels.EndorsementViewModel;
+import com.RDS.skilltree.viewmodels.RdsUserViewModel;
+import com.RDS.skilltree.viewmodels.UserViewModel;
+
+public class TestDataHelper {
+ public static RdsGetUserDetailsResDto createUserDetails(String userId, boolean isSuperUser) {
+ RdsUserViewModel user = new RdsUserViewModel();
+ user.setId(userId);
+
+ // set a dummy name
+ user.setFirst_name(userId);
+ user.setLast_name(userId);
+
+ RdsUserViewModel.Roles roles = new RdsUserViewModel.Roles();
+ roles.setSuper_user(isSuperUser);
+ user.setRoles(roles);
+
+ RdsGetUserDetailsResDto userDetails = new RdsGetUserDetailsResDto();
+ userDetails.setUser(user);
+ return userDetails;
+ }
+
+ public static EndorsementViewModel createEndorsementViewModel(
+ Endorsement endorsement, RdsService rdsService) {
+ RdsUserViewModel endorseDetail =
+ rdsService.getUserDetails(endorsement.getEndorseId()).getUser();
+ RdsUserViewModel endorserDetail =
+ rdsService.getUserDetails(endorsement.getEndorserId()).getUser();
+
+ UserViewModel endorse =
+ UserViewModel.builder()
+ .id(endorseDetail.getId())
+ .name(endorseDetail.getFirst_name() + " " + endorseDetail.getLast_name())
+ .build();
+ UserViewModel endorser =
+ UserViewModel.builder()
+ .id(endorserDetail.getId())
+ .name(endorserDetail.getFirst_name() + " " + endorserDetail.getLast_name())
+ .build();
+
+ return EndorsementViewModel.toViewModel(endorsement, endorse, endorser);
+ }
+
+ public static Endorsement createEndorsement(
+ Skill skill, String endorseId, String endorserId, String message) {
+ return Endorsement.builder()
+ .endorserId(endorserId)
+ .endorseId(endorseId)
+ .skill(skill)
+ .message(message)
+ .build();
+ }
+
+ public static Skill createSkill(String skillName, String userId) {
+ return Skill.builder().name(skillName).createdBy(userId).type(SkillTypeEnum.ATOMIC).build();
+ }
+}
diff --git a/skill-tree/src/test/java/utils/WithCustomMockUser.java b/skill-tree/src/test/java/utils/WithCustomMockUser.java
new file mode 100644
index 00000000..c913d2fd
--- /dev/null
+++ b/skill-tree/src/test/java/utils/WithCustomMockUser.java
@@ -0,0 +1,13 @@
+package utils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import org.springframework.security.test.context.support.WithSecurityContext;
+
+@Retention(RetentionPolicy.RUNTIME)
+@WithSecurityContext(factory = CustomSecurityContextFactory.class)
+public @interface WithCustomMockUser {
+ String username() default "test-user";
+
+ String[] authorities() default {};
+}