From 36f6a577b110dbe0f7a25453564b41597112264a Mon Sep 17 00:00:00 2001
From: Shyam Vishwakarma <144812100+Shyam-Vishwakarma@users.noreply.github.com>
Date: Wed, 23 Oct 2024 18:15:58 +0530
Subject: [PATCH 01/10] Add integration tests for GET v1/skills route (#161)
* add integration tests for GET v1/skills, add test DB configuration
* fix format violation
---
.../resources/application-test.properties | 8 ++
.../skills/GetAllSkillsIntegrationTest.java | 95 +++++++++++++++++++
2 files changed, 103 insertions(+)
create mode 100644 skill-tree/src/main/resources/application-test.properties
create mode 100644 skill-tree/src/test/java/com/RDS/skilltree/skills/GetAllSkillsIntegrationTest.java
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..02c453f3
--- /dev/null
+++ b/skill-tree/src/main/resources/application-test.properties
@@ -0,0 +1,8 @@
+cookieName=rds-session-v2-development
+
+spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/skilltreetestdb
+spring.datasource.username=${MYSQL_DB_USERNAME}
+spring.datasource.password=${MYSQL_DB_PASSWORD}
+spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
+spring.jpa.hibernate.ddl-auto=create-drop
+spring.jpa.show-sql=true
diff --git a/skill-tree/src/test/java/com/RDS/skilltree/skills/GetAllSkillsIntegrationTest.java b/skill-tree/src/test/java/com/RDS/skilltree/skills/GetAllSkillsIntegrationTest.java
new file mode 100644
index 00000000..b16ac200
--- /dev/null
+++ b/skill-tree/src/test/java/com/RDS/skilltree/skills/GetAllSkillsIntegrationTest.java
@@ -0,0 +1,95 @@
+package com.RDS.skilltree.skills;
+
+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 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.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+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.MockMvcResultHandlers;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+@ActiveProfiles("test")
+public class GetAllSkillsIntegrationTest {
+
+ @Autowired private SkillService skillService;
+
+ @Autowired private SkillRepository skillRepository;
+
+ @Autowired private MockMvc mockMvc;
+
+ private Cookie authCookie;
+
+ @BeforeEach
+ public void setUp() {
+ authCookie =
+ new Cookie(
+ "rds-session-v2-development",
+ "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJzOXpRVW00WGJWRXo3eHpSa2FadiIsInJvbGUiOiJzdXBlcl91c2VyIiwiaWF0IjoxNzI4NjY0NjA2LCJleHAiOjE3MzEyNTY2MDZ9.EyOFKrVcbleuTjUGic3GzOzYRDoLU4IShyoboe0MHlvWFOAfU2pchpXLE4NcyvdGUZ_tvoUecHd4kUkR8MkhxnkRNU3HE7N-1c1tFeYXZL0KfScJE9YzDXAl113Hx3eZVvYbhNjNUttbDlH4s_kR6YABC3sdbLGKEiLfmp9VeAs");
+
+ 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));
+ }
+
+ @Test
+ @DisplayName("happy flow - returns all skills that are in db")
+ public void getAllSkillsHappyFlow() throws Exception {
+
+ mockMvc
+ .perform(
+ MockMvcRequestBuilders.get("/v1/skills")
+ .cookie(authCookie)
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.jsonPath("$[0].name").value("Java"))
+ .andExpect(MockMvcResultMatchers.jsonPath("$[1].name").value("Springboot"))
+ .andDo(MockMvcResultHandlers.print());
+ }
+
+ @Test
+ @DisplayName("if no skills available, return empty list")
+ public void noSkillsAvailable_shouldReturnEmptyList() throws Exception {
+ skillRepository.deleteAll();
+
+ mockMvc
+ .perform(
+ MockMvcRequestBuilders.get("/v1/skills")
+ .cookie(authCookie)
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.jsonPath("$").isEmpty());
+ }
+
+ @Test
+ @DisplayName("if invalid cookie, return 401")
+ public void ifInvalidCoolie_returnUnauthorized() throws Exception {
+ mockMvc
+ .perform(
+ MockMvcRequestBuilders.get("/v1/skills")
+ .cookie(new Cookie("cookie1", "eyJhbGciOiJSUz.eyJhbGciOiJSUz.EyJhbGciOiJSUz"))
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(MockMvcResultMatchers.status().isUnauthorized());
+ }
+}
From 7e27cc5ed5900c17d5f0a68b9df258ca7f383608 Mon Sep 17 00:00:00 2001
From: Shyam Vishwakarma <144812100+Shyam-Vishwakarma@users.noreply.github.com>
Date: Wed, 27 Nov 2024 23:26:26 +0530
Subject: [PATCH 02/10] Add integration tests for GET /v1/skills/requests route
(#169)
* add integration tests for GET v1/skills, add test DB configuration
* fix format violation
* Update test configuration to create schema using Flyway
* Add integration test for v1/skills/requests route
* Add custom result matcher for mockMvc responses
* Add security config for test environment to mock logged in users
* Add Use of CustomMockUser instead of AuthCookie
* Add spring-security test dependency
---
skill-tree/pom.xml | 5 +
.../resources/application-test.properties | 6 +-
.../GetAllSkillRequestIntegrationTest.java | 285 ++++++++++++++++++
.../skills/GetAllSkillsIntegrationTest.java | 34 +--
.../test/java/utils/CustomResultMatchers.java | 99 ++++++
.../utils/CustomSecurityContextFactory.java | 39 +++
.../test/java/utils/WithCustomMockUser.java | 13 +
7 files changed, 462 insertions(+), 19 deletions(-)
create mode 100644 skill-tree/src/test/java/com/RDS/skilltree/skills/GetAllSkillRequestIntegrationTest.java
create mode 100644 skill-tree/src/test/java/utils/CustomResultMatchers.java
create mode 100644 skill-tree/src/test/java/utils/CustomSecurityContextFactory.java
create mode 100644 skill-tree/src/test/java/utils/WithCustomMockUser.java
diff --git a/skill-tree/pom.xml b/skill-tree/pom.xml
index ac132714..c840309b 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
+
diff --git a/skill-tree/src/main/resources/application-test.properties b/skill-tree/src/main/resources/application-test.properties
index 02c453f3..5d9cfc71 100644
--- a/skill-tree/src/main/resources/application-test.properties
+++ b/skill-tree/src/main/resources/application-test.properties
@@ -3,6 +3,10 @@ cookieName=rds-session-v2-development
spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/skilltreetestdb
spring.datasource.username=${MYSQL_DB_USERNAME}
spring.datasource.password=${MYSQL_DB_PASSWORD}
+
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
-spring.jpa.hibernate.ddl-auto=create-drop
+
+spring.flyway.enabled=true
+spring.flyway.locations=classpath:db/migrations
+
spring.jpa.show-sql=true
diff --git a/skill-tree/src/test/java/com/RDS/skilltree/skills/GetAllSkillRequestIntegrationTest.java b/skill-tree/src/test/java/com/RDS/skilltree/skills/GetAllSkillRequestIntegrationTest.java
new file mode 100644
index 00000000..90dbcd91
--- /dev/null
+++ b/skill-tree/src/test/java/com/RDS/skilltree/skills/GetAllSkillRequestIntegrationTest.java
@@ -0,0 +1,285 @@
+package com.RDS.skilltree.skills;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+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.*;
+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.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.MockMvcResultHandlers;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+import utils.CustomResultMatchers;
+import utils.WithCustomMockUser;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+@ActiveProfiles("test")
+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))
+ .andDo(MockMvcResultHandlers.print())
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .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))
+ .andDo(MockMvcResultHandlers.print())
+ .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("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))
+ .andDo(MockMvcResultHandlers.print())
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .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("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))
+ .andDo(MockMvcResultHandlers.print())
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .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))
+ .andDo(MockMvcResultHandlers.print())
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .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))
+ .andDo(MockMvcResultHandlers.print())
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .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))
+ .andDo(MockMvcResultHandlers.print())
+ .andExpect(MockMvcResultMatchers.status().isUnauthorized());
+ }
+}
diff --git a/skill-tree/src/test/java/com/RDS/skilltree/skills/GetAllSkillsIntegrationTest.java b/skill-tree/src/test/java/com/RDS/skilltree/skills/GetAllSkillsIntegrationTest.java
index b16ac200..cca60e09 100644
--- a/skill-tree/src/test/java/com/RDS/skilltree/skills/GetAllSkillsIntegrationTest.java
+++ b/skill-tree/src/test/java/com/RDS/skilltree/skills/GetAllSkillsIntegrationTest.java
@@ -18,6 +18,7 @@
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+import utils.WithCustomMockUser;
@SpringBootTest
@AutoConfigureMockMvc
@@ -30,15 +31,10 @@ public class GetAllSkillsIntegrationTest {
@Autowired private MockMvc mockMvc;
- private Cookie authCookie;
+ private final String route = "/v1/skills";
@BeforeEach
public void setUp() {
- authCookie =
- new Cookie(
- "rds-session-v2-development",
- "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJzOXpRVW00WGJWRXo3eHpSa2FadiIsInJvbGUiOiJzdXBlcl91c2VyIiwiaWF0IjoxNzI4NjY0NjA2LCJleHAiOjE3MzEyNTY2MDZ9.EyOFKrVcbleuTjUGic3GzOzYRDoLU4IShyoboe0MHlvWFOAfU2pchpXLE4NcyvdGUZ_tvoUecHd4kUkR8MkhxnkRNU3HE7N-1c1tFeYXZL0KfScJE9YzDXAl113Hx3eZVvYbhNjNUttbDlH4s_kR6YABC3sdbLGKEiLfmp9VeAs");
-
skillRepository.deleteAll();
Skill skill1 = new Skill();
skill1.setName("Java");
@@ -54,14 +50,13 @@ public void setUp() {
}
@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("/v1/skills")
- .cookie(authCookie)
- .accept(MediaType.APPLICATION_JSON))
+ .perform(MockMvcRequestBuilders.get(route).accept(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$[0].name").value("Java"))
.andExpect(MockMvcResultMatchers.jsonPath("$[1].name").value("Springboot"))
@@ -70,14 +65,14 @@ public void getAllSkillsHappyFlow() throws Exception {
@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("/v1/skills")
- .cookie(authCookie)
- .accept(MediaType.APPLICATION_JSON))
+ .perform(MockMvcRequestBuilders.get(route).accept(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$").isEmpty());
}
@@ -85,11 +80,14 @@ public void noSkillsAvailable_shouldReturnEmptyList() throws Exception {
@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("/v1/skills")
- .cookie(new Cookie("cookie1", "eyJhbGciOiJSUz.eyJhbGciOiJSUz.EyJhbGciOiJSUz"))
- .accept(MediaType.APPLICATION_JSON))
+ MockMvcRequestBuilders.get(route).cookie(authCookie).accept(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isUnauthorized());
}
}
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..5eb85e6c
--- /dev/null
+++ b/skill-tree/src/test/java/utils/CustomResultMatchers.java
@@ -0,0 +1,99 @@
+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);
+ };
+ }
+
+ 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/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 {};
+}
From 52cb8d97b0bfa48df285a24817521e2a1446cb46 Mon Sep 17 00:00:00 2001
From: Shyam Vishwakarma <144812100+Shyam-Vishwakarma@users.noreply.github.com>
Date: Sat, 1 Feb 2025 16:57:49 +0530
Subject: [PATCH 03/10] add integration test for POST
/v1/skills/requests/{skillId}/action (#177)
---
.../SkillRequestActionIntegrationTest.java | 200 ++++++++++++++++++
1 file changed, 200 insertions(+)
create mode 100644 skill-tree/src/test/java/com/RDS/skilltree/skills/SkillRequestActionIntegrationTest.java
diff --git a/skill-tree/src/test/java/com/RDS/skilltree/skills/SkillRequestActionIntegrationTest.java b/skill-tree/src/test/java/com/RDS/skilltree/skills/SkillRequestActionIntegrationTest.java
new file mode 100644
index 00000000..dca1d197
--- /dev/null
+++ b/skill-tree/src/test/java/com/RDS/skilltree/skills/SkillRequestActionIntegrationTest.java
@@ -0,0 +1,200 @@
+package com.RDS.skilltree.skills;
+
+import com.RDS.skilltree.dtos.RdsGetUserDetailsResDto;
+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.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+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.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.MockMvcResultHandlers;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+import utils.WithCustomMockUser;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+@ActiveProfiles("test")
+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 {
+ String requestBody = """
+ {
+ "endorseId": "test-user-id",
+ "action": "APPROVED"
+ }
+ """;
+
+ mockMvc.perform(MockMvcRequestBuilders.post(baseRoute + "/" + skill.getId() + "/action")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(String.valueOf(requestBody)))
+ .andDo(MockMvcResultHandlers.print())
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .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 {
+ String requestBody = """
+ {
+ "endorseId": "test-user-id",
+ "action": "REJECTED"
+ }
+ """;
+
+ mockMvc.perform(MockMvcRequestBuilders.post(baseRoute + "/" + skill.getId() + "/action")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(requestBody))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.jsonPath("$.message").value("rejected"));
+
+ // Verify the status was updatedUserSkill 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 {
+ String requestBody = """
+ {
+ "endorseId": "test-user-id",
+ "action": "APPROVED"
+ }
+ """;
+
+ mockMvc.perform(MockMvcRequestBuilders.post(baseRoute + "/123/action")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(requestBody))
+ .andExpect(MockMvcResultMatchers.status().isNotFound());
+ }
+
+ @Test
+ @DisplayName("Error case - Request with non-existent user ID")
+ @WithCustomMockUser(username = "super-user-id", authorities = {"SUPERUSER"})
+ public void approveSkillRequest_NonExistentUserId_ShouldFail() throws Exception {
+ String requestBody = """
+ {
+ "endorseId": "non-existent-user",
+ "action": "APPROVED"
+ }
+ """;
+
+ mockMvc.perform(MockMvcRequestBuilders.post(baseRoute + "/" + skill.getId() + "/action")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(requestBody))
+ .andExpect(MockMvcResultMatchers.status().isNotFound());
+ }
+
+ @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().isBadRequest());
+ }
+
+ @Test
+ @DisplayName("Authorization test - Non-super user cannot access endpoint")
+ @WithCustomMockUser(username = "normal-user", authorities = {"USER"})
+ public void approveSkillRequest_NonSuperUser_ShouldFail() throws Exception {
+ String requestBody = """
+ {
+ "endorseId": "test-user-id",
+ "action": "APPROVED"
+ }
+ """;
+
+ mockMvc.perform(MockMvcRequestBuilders.post(baseRoute + "/" + skill.getId() + "/action")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(requestBody))
+ .andExpect(MockMvcResultMatchers.status().isForbidden());
+ }
+}
\ No newline at end of file
From 907bf83176accf235f1fdf8a6f7114c170a836c9 Mon Sep 17 00:00:00 2001
From: Shyam Vishwakarma <144812100+Shyam-Vishwakarma@users.noreply.github.com>
Date: Sat, 1 Mar 2025 03:55:01 +0530
Subject: [PATCH 04/10] Test: Integration Test for POST /v1/skills (#182)
* change status code from 200 to 201
* add integration tests for POST v1/skills
---
.../com/RDS/skilltree/apis/SkillsApi.java | 2 +-
.../skills/CreateSkillIntegrationTest.java | 159 ++++++++++++++++++
2 files changed, 160 insertions(+), 1 deletion(-)
create mode 100644 skill-tree/src/test/java/com/RDS/skilltree/skills/CreateSkillIntegrationTest.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..0515e27e 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
@@ -57,7 +57,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/test/java/com/RDS/skilltree/skills/CreateSkillIntegrationTest.java b/skill-tree/src/test/java/com/RDS/skilltree/skills/CreateSkillIntegrationTest.java
new file mode 100644
index 00000000..4c2f56d9
--- /dev/null
+++ b/skill-tree/src/test/java/com/RDS/skilltree/skills/CreateSkillIntegrationTest.java
@@ -0,0 +1,159 @@
+package com.RDS.skilltree.skills;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+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.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.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.MockMvcResultHandlers;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+import utils.WithCustomMockUser;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+@ActiveProfiles("test")
+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))
+ .andDo(MockMvcResultHandlers.print())
+ .andExpect(MockMvcResultMatchers.status().isCreated())
+ .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))
+ .andDo(MockMvcResultHandlers.print())
+ .andExpect(MockMvcResultMatchers.status().isConflict());
+ }
+
+ @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().isBadRequest());
+ }
+
+ @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().is5xxServerError());
+ }
+
+ @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().isForbidden());
+ }
+}
From c71d76729cca3d59c8904d6bf4e64c894d7b7dd8 Mon Sep 17 00:00:00 2001
From: Shyam Vishwakarma <144812100+Shyam-Vishwakarma@users.noreply.github.com>
Date: Fri, 4 Apr 2025 18:51:51 +0530
Subject: [PATCH 05/10] fix: update formatting of integration tests for POST
/v1/skills/requests/{skillId}/action (#188)
* fix:update formatting of request-body
* replace hardcoded JSON strings by serializing object of SkillRequestActionRequestDto
---
.../SkillRequestActionIntegrationTest.java | 169 ++++++++++--------
1 file changed, 90 insertions(+), 79 deletions(-)
diff --git a/skill-tree/src/test/java/com/RDS/skilltree/skills/SkillRequestActionIntegrationTest.java b/skill-tree/src/test/java/com/RDS/skilltree/skills/SkillRequestActionIntegrationTest.java
index dca1d197..1510741c 100644
--- a/skill-tree/src/test/java/com/RDS/skilltree/skills/SkillRequestActionIntegrationTest.java
+++ b/skill-tree/src/test/java/com/RDS/skilltree/skills/SkillRequestActionIntegrationTest.java
@@ -1,6 +1,11 @@
package com.RDS.skilltree.skills;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
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;
@@ -28,26 +33,16 @@
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import utils.WithCustomMockUser;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
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;
+ @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";
@@ -92,109 +87,125 @@ void setUp() {
@Test
@DisplayName("Happy flow - Super user can approve a skill request")
- @WithCustomMockUser(username = "super-user-id", authorities = {"SUPERUSER"})
+ @WithCustomMockUser(
+ username = "super-user-id",
+ authorities = {"SUPERUSER"})
public void approveSkillRequest_validRequest_shouldApproveSkillRequest() throws Exception {
- String requestBody = """
- {
- "endorseId": "test-user-id",
- "action": "APPROVED"
- }
- """;
-
- mockMvc.perform(MockMvcRequestBuilders.post(baseRoute + "/" + skill.getId() + "/action")
- .contentType(MediaType.APPLICATION_JSON)
- .content(String.valueOf(requestBody)))
+ 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))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.message").value("approved"));
// Verify the status was updated in database
- UserSkills updatedUserSkill = userSkillRepository.findByUserIdAndSkillId("test-user-id", skill.getId()).get(0);
+ 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"})
+ @WithCustomMockUser(
+ username = "super-user-id",
+ authorities = {"SUPERUSER"})
public void rejectSkillRequest_validRequest_shouldRejectSkillRequest() throws Exception {
- String requestBody = """
- {
- "endorseId": "test-user-id",
- "action": "REJECTED"
- }
- """;
-
- mockMvc.perform(MockMvcRequestBuilders.post(baseRoute + "/" + skill.getId() + "/action")
- .contentType(MediaType.APPLICATION_JSON)
- .content(requestBody))
+ 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().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.message").value("rejected"));
- // Verify the status was updatedUserSkill in database
- UserSkills updatedUserSkill = userSkillRepository.findByUserIdAndSkillId("test-user-id", skill.getId()).get(0);
+ // 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"})
+ @WithCustomMockUser(
+ username = "super-user-id",
+ authorities = {"SUPERUSER"})
public void approveSkillRequest_NonExistentSkillId_ShouldFail() throws Exception {
- String requestBody = """
- {
- "endorseId": "test-user-id",
- "action": "APPROVED"
- }
- """;
-
- mockMvc.perform(MockMvcRequestBuilders.post(baseRoute + "/123/action")
- .contentType(MediaType.APPLICATION_JSON)
- .content(requestBody))
+ 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().isNotFound());
}
@Test
@DisplayName("Error case - Request with non-existent user ID")
- @WithCustomMockUser(username = "super-user-id", authorities = {"SUPERUSER"})
+ @WithCustomMockUser(
+ username = "super-user-id",
+ authorities = {"SUPERUSER"})
public void approveSkillRequest_NonExistentUserId_ShouldFail() throws Exception {
- String requestBody = """
- {
- "endorseId": "non-existent-user",
- "action": "APPROVED"
- }
- """;
-
- mockMvc.perform(MockMvcRequestBuilders.post(baseRoute + "/" + skill.getId() + "/action")
- .contentType(MediaType.APPLICATION_JSON)
- .content(requestBody))
+ 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().isNotFound());
}
@Test
@DisplayName("Validation test - Missing required fields")
- @WithCustomMockUser(username = "super-user-id", authorities = {"SUPERUSER"})
+ @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))
+ mockMvc
+ .perform(
+ MockMvcRequestBuilders.post(baseRoute + "/" + skill.getId() + "/action")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(requestBody))
.andExpect(MockMvcResultMatchers.status().isBadRequest());
}
@Test
@DisplayName("Authorization test - Non-super user cannot access endpoint")
- @WithCustomMockUser(username = "normal-user", authorities = {"USER"})
+ @WithCustomMockUser(
+ username = "normal-user",
+ authorities = {"USER"})
public void approveSkillRequest_NonSuperUser_ShouldFail() throws Exception {
- String requestBody = """
- {
- "endorseId": "test-user-id",
- "action": "APPROVED"
- }
- """;
-
- mockMvc.perform(MockMvcRequestBuilders.post(baseRoute + "/" + skill.getId() + "/action")
- .contentType(MediaType.APPLICATION_JSON)
- .content(requestBody))
+ 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().isForbidden());
}
-}
\ No newline at end of file
+}
From 637fdc1444d424ce16e88e8474905ef236ce3e81 Mon Sep 17 00:00:00 2001
From: Shyam Vishwakarma <144812100+Shyam-Vishwakarma@users.noreply.github.com>
Date: Sat, 19 Apr 2025 00:59:32 +0530
Subject: [PATCH 06/10] fix: setup integration tests using test containers
(#192)
* fix: update failsafe config, add dotenv dependency
* fix: replace hardcoded test-db-name with env variable
* fix: move integration test classes to integration package
* add test container manager
* fix: remove test-db config as it is not required
* fix: remove print statements
* fix: remove disabled from unit tests, update config of surefire to locate unite tests correctly
* fix: upgrade github actions runner version to v4
* fix: make version of mysql db to a more specific version tag
* fix: update config to include multilevel folder
* fix: update expected status to status code
* fix: remove unnecessary config and functions
* fix: fix typos in commands
---------
Co-authored-by: yash raj
---
.github/workflows/ci.yml | 26 +++++++-------
skill-tree/pom.xml | 4 +--
.../resources/application-test.properties | 9 -----
.../RDS/skilltree/TestContainerManager.java | 12 +++++++
.../skills/CreateSkillIntegrationTest.java | 23 ++++++------
.../GetAllSkillRequestIntegrationTest.java | 34 +++++++++---------
.../skills/GetAllSkillsIntegrationTest.java | 35 ++++++++++++++-----
.../SkillRequestActionIntegrationTest.java | 27 +++++++-------
.../utils/UUIDValidationInterceptorTest.java | 12 ++-----
9 files changed, 95 insertions(+), 87 deletions(-)
create mode 100644 skill-tree/src/test/java/com/RDS/skilltree/TestContainerManager.java
rename skill-tree/src/test/java/com/RDS/skilltree/{ => integration}/skills/CreateSkillIntegrationTest.java (89%)
rename skill-tree/src/test/java/com/RDS/skilltree/{ => integration}/skills/GetAllSkillRequestIntegrationTest.java (92%)
rename skill-tree/src/test/java/com/RDS/skilltree/{ => integration}/skills/GetAllSkillsIntegrationTest.java (71%)
rename skill-tree/src/test/java/com/RDS/skilltree/{ => integration}/skills/SkillRequestActionIntegrationTest.java (91%)
rename skill-tree/src/test/java/com/RDS/skilltree/{ => unit}/utils/UUIDValidationInterceptorTest.java (92%)
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 c840309b..d53dee5a 100644
--- a/skill-tree/pom.xml
+++ b/skill-tree/pom.xml
@@ -219,7 +219,7 @@
${skip-ut}
${skip-tests}
- **/unit/*.java
+ **/unit/**/*.java
@@ -231,7 +231,7 @@
${skip-tests}
- **/integration/*.java
+ **/integration/**/*.java
diff --git a/skill-tree/src/main/resources/application-test.properties b/skill-tree/src/main/resources/application-test.properties
index 5d9cfc71..fd45b40c 100644
--- a/skill-tree/src/main/resources/application-test.properties
+++ b/skill-tree/src/main/resources/application-test.properties
@@ -1,12 +1,3 @@
cookieName=rds-session-v2-development
-
-spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/skilltreetestdb
-spring.datasource.username=${MYSQL_DB_USERNAME}
-spring.datasource.password=${MYSQL_DB_PASSWORD}
-
-spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
-
spring.flyway.enabled=true
spring.flyway.locations=classpath:db/migrations
-
-spring.jpa.show-sql=true
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..e37ab25c
--- /dev/null
+++ b/skill-tree/src/test/java/com/RDS/skilltree/TestContainerManager.java
@@ -0,0 +1,12 @@
+package com.RDS.skilltree;
+
+import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
+import org.testcontainers.containers.MySQLContainer;
+
+public abstract class TestContainerManager {
+ @ServiceConnection static MySQLContainer> mySQLContainer = new MySQLContainer<>("mysql:8.1.0");
+
+ static {
+ mySQLContainer.start();
+ }
+}
diff --git a/skill-tree/src/test/java/com/RDS/skilltree/skills/CreateSkillIntegrationTest.java b/skill-tree/src/test/java/com/RDS/skilltree/integration/skills/CreateSkillIntegrationTest.java
similarity index 89%
rename from skill-tree/src/test/java/com/RDS/skilltree/skills/CreateSkillIntegrationTest.java
rename to skill-tree/src/test/java/com/RDS/skilltree/integration/skills/CreateSkillIntegrationTest.java
index 4c2f56d9..941543e5 100644
--- a/skill-tree/src/test/java/com/RDS/skilltree/skills/CreateSkillIntegrationTest.java
+++ b/skill-tree/src/test/java/com/RDS/skilltree/integration/skills/CreateSkillIntegrationTest.java
@@ -1,9 +1,10 @@
-package com.RDS.skilltree.skills;
+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;
@@ -16,6 +17,7 @@
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;
@@ -24,14 +26,14 @@
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.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import utils.WithCustomMockUser;
-@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
-public class CreateSkillIntegrationTest {
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
+public class CreateSkillIntegrationTest extends TestContainerManager {
@Autowired private MockMvc mockMvc;
@Autowired private SkillRepository skillRepository;
@MockBean private RdsService rdsService;
@@ -77,13 +79,11 @@ public void createSkill_validRequest_shouldCreateSkill() throws Exception {
MockMvcRequestBuilders.post(baseRoute)
.contentType(MediaType.APPLICATION_JSON)
.content(requestBody))
- .andDo(MockMvcResultHandlers.print())
- .andExpect(MockMvcResultMatchers.status().isCreated())
+ .andExpect(MockMvcResultMatchers.status().is(201))
.andExpect(MockMvcResultMatchers.jsonPath("$.name").value("Java"))
.andExpect(MockMvcResultMatchers.jsonPath("$.type").value("ATOMIC"));
assert skillRepository.existsByName("Java");
- ;
}
@Test
@@ -105,8 +105,7 @@ public void createSkill_duplicateName_shouldFail() throws Exception {
MockMvcRequestBuilders.post(baseRoute)
.contentType(MediaType.APPLICATION_JSON)
.content(requestBody))
- .andDo(MockMvcResultHandlers.print())
- .andExpect(MockMvcResultMatchers.status().isConflict());
+ .andExpect(MockMvcResultMatchers.status().is(409));
}
@Test
@@ -122,7 +121,7 @@ public void createSkill_missingRequiredFields_shouldFail() throws Exception {
MockMvcRequestBuilders.post(baseRoute)
.contentType(MediaType.APPLICATION_JSON)
.content(requestBody))
- .andExpect(MockMvcResultMatchers.status().isBadRequest());
+ .andExpect(MockMvcResultMatchers.status().is(400));
}
@Test
@@ -138,7 +137,7 @@ public void createSkill_invalidSkillType_shouldFail() throws Exception {
MockMvcRequestBuilders.post(baseRoute)
.contentType(MediaType.APPLICATION_JSON)
.content(requestBody))
- .andExpect(MockMvcResultMatchers.status().is5xxServerError());
+ .andExpect(MockMvcResultMatchers.status().is(500));
}
@Test
@@ -154,6 +153,6 @@ public void createSkill_nonSuperUser_shouldFail() throws Exception {
MockMvcRequestBuilders.post(baseRoute)
.contentType(MediaType.APPLICATION_JSON)
.content(requestBody))
- .andExpect(MockMvcResultMatchers.status().isForbidden());
+ .andExpect(MockMvcResultMatchers.status().is(403));
}
}
diff --git a/skill-tree/src/test/java/com/RDS/skilltree/skills/GetAllSkillRequestIntegrationTest.java b/skill-tree/src/test/java/com/RDS/skilltree/integration/skills/GetAllSkillRequestIntegrationTest.java
similarity index 92%
rename from skill-tree/src/test/java/com/RDS/skilltree/skills/GetAllSkillRequestIntegrationTest.java
rename to skill-tree/src/test/java/com/RDS/skilltree/integration/skills/GetAllSkillRequestIntegrationTest.java
index 90dbcd91..ac2686ab 100644
--- a/skill-tree/src/test/java/com/RDS/skilltree/skills/GetAllSkillRequestIntegrationTest.java
+++ b/skill-tree/src/test/java/com/RDS/skilltree/integration/skills/GetAllSkillRequestIntegrationTest.java
@@ -1,9 +1,10 @@
-package com.RDS.skilltree.skills;
+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;
@@ -18,7 +19,10 @@
import com.RDS.skilltree.viewmodels.RdsUserViewModel;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.Cookie;
-import org.junit.jupiter.api.*;
+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;
@@ -27,15 +31,15 @@
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.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import utils.CustomResultMatchers;
import utils.WithCustomMockUser;
-@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
-public class GetAllSkillRequestIntegrationTest {
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
+public class GetAllSkillRequestIntegrationTest extends TestContainerManager {
@Autowired private MockMvc mockMvc;
@Autowired private UserSkillRepository userSkillRepository;
@@ -162,8 +166,7 @@ void setUp() {
public void getAllRequests_asSuperUser_shouldReturnAllRequests() throws Exception {
mockMvc
.perform(MockMvcRequestBuilders.get(route).contentType(MediaType.APPLICATION_JSON))
- .andDo(MockMvcResultHandlers.print())
- .andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.status().is(200))
.andExpect(CustomResultMatchers.hasSkillRequest("Java", "user-id", "PENDING"))
.andExpect(
CustomResultMatchers.hasEndorsement(
@@ -189,7 +192,7 @@ public void getAllRequests_asSuperUser_shouldReturnAllRequests() throws Exceptio
public void getAllRequests_asNormalUser_shouldReturnAllRequestsByEndorser() throws Exception {
mockMvc
.perform(MockMvcRequestBuilders.get(route).contentType(MediaType.APPLICATION_JSON))
- .andDo(MockMvcResultHandlers.print())
+ .andExpect(MockMvcResultMatchers.status().is(200))
.andExpect(CustomResultMatchers.hasSkillRequest("Springboot", "user-id", "APPROVED"))
.andExpect(
CustomResultMatchers.hasEndorsement(
@@ -208,8 +211,7 @@ public void getAllRequests_ByStatus_ShouldReturnFilteredRequests() throws Except
.perform(
MockMvcRequestBuilders.get(route + "?status=APPROVED")
.contentType(MediaType.APPLICATION_JSON))
- .andDo(MockMvcResultHandlers.print())
- .andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.status().is(200))
.andExpect(CustomResultMatchers.hasSkillRequest("Springboot", "user-id", "APPROVED"))
.andExpect(
CustomResultMatchers.hasEndorsement(
@@ -226,8 +228,7 @@ public void getAllRequests_ByStatus_ShouldReturnFilteredRequests() throws Except
public void noSkillRequestsEndorsedByUser_ShouldReturnEmptyLists() throws Exception {
mockMvc
.perform(MockMvcRequestBuilders.get(route).contentType(MediaType.APPLICATION_JSON))
- .andDo(MockMvcResultHandlers.print())
- .andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.status().is(200))
.andExpect(MockMvcResultMatchers.jsonPath("$.requests").isEmpty())
.andExpect(MockMvcResultMatchers.jsonPath("$.users").isEmpty());
}
@@ -242,8 +243,7 @@ public void noMatchingRequestsByStatus_ShouldReturnEmptyLists() throws Exception
.perform(
MockMvcRequestBuilders.get(route + "?status=REJECTED")
.contentType(MediaType.APPLICATION_JSON))
- .andDo(MockMvcResultHandlers.print())
- .andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.status().is(200))
.andExpect(MockMvcResultMatchers.jsonPath("$.requests").isEmpty())
.andExpect(MockMvcResultMatchers.jsonPath("$.users").isEmpty());
}
@@ -260,8 +260,7 @@ public void getAllRequests_NoData_ShouldReturnEmptyLists() throws Exception {
mockMvc
.perform(MockMvcRequestBuilders.get(route).contentType(MediaType.APPLICATION_JSON))
- .andDo(MockMvcResultHandlers.print())
- .andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.status().is(200))
.andExpect(MockMvcResultMatchers.jsonPath("$.requests").isEmpty())
.andExpect(MockMvcResultMatchers.jsonPath("$.users").isEmpty());
}
@@ -279,7 +278,6 @@ public void ifInvalidCoolie_ShouldReturnUnauthorized() throws Exception {
MockMvcRequestBuilders.get(route)
.cookie(authCookie)
.contentType(MediaType.APPLICATION_JSON))
- .andDo(MockMvcResultHandlers.print())
- .andExpect(MockMvcResultMatchers.status().isUnauthorized());
+ .andExpect(MockMvcResultMatchers.status().is(401));
}
}
diff --git a/skill-tree/src/test/java/com/RDS/skilltree/skills/GetAllSkillsIntegrationTest.java b/skill-tree/src/test/java/com/RDS/skilltree/integration/skills/GetAllSkillsIntegrationTest.java
similarity index 71%
rename from skill-tree/src/test/java/com/RDS/skilltree/skills/GetAllSkillsIntegrationTest.java
rename to skill-tree/src/test/java/com/RDS/skilltree/integration/skills/GetAllSkillsIntegrationTest.java
index cca60e09..ba475422 100644
--- a/skill-tree/src/test/java/com/RDS/skilltree/skills/GetAllSkillsIntegrationTest.java
+++ b/skill-tree/src/test/java/com/RDS/skilltree/integration/skills/GetAllSkillsIntegrationTest.java
@@ -1,35 +1,48 @@
-package com.RDS.skilltree.skills;
+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.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.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import utils.WithCustomMockUser;
-@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
-public class GetAllSkillsIntegrationTest {
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
+public class GetAllSkillsIntegrationTest extends TestContainerManager {
@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";
@@ -47,6 +60,11 @@ public void setUp() {
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
@@ -57,10 +75,9 @@ public void setUp() {
public void getAllSkillsHappyFlow() throws Exception {
mockMvc
.perform(MockMvcRequestBuilders.get(route).accept(MediaType.APPLICATION_JSON))
- .andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.status().is(200))
.andExpect(MockMvcResultMatchers.jsonPath("$[0].name").value("Java"))
- .andExpect(MockMvcResultMatchers.jsonPath("$[1].name").value("Springboot"))
- .andDo(MockMvcResultHandlers.print());
+ .andExpect(MockMvcResultMatchers.jsonPath("$[1].name").value("Springboot"));
}
@Test
@@ -73,7 +90,7 @@ public void noSkillsAvailable_shouldReturnEmptyList() throws Exception {
mockMvc
.perform(MockMvcRequestBuilders.get(route).accept(MediaType.APPLICATION_JSON))
- .andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.status().is(200))
.andExpect(MockMvcResultMatchers.jsonPath("$").isEmpty());
}
@@ -88,6 +105,6 @@ public void ifInvalidCoolie_returnUnauthorized() throws Exception {
mockMvc
.perform(
MockMvcRequestBuilders.get(route).cookie(authCookie).accept(MediaType.APPLICATION_JSON))
- .andExpect(MockMvcResultMatchers.status().isUnauthorized());
+ .andExpect(MockMvcResultMatchers.status().is(401));
}
}
diff --git a/skill-tree/src/test/java/com/RDS/skilltree/skills/SkillRequestActionIntegrationTest.java b/skill-tree/src/test/java/com/RDS/skilltree/integration/skills/SkillRequestActionIntegrationTest.java
similarity index 91%
rename from skill-tree/src/test/java/com/RDS/skilltree/skills/SkillRequestActionIntegrationTest.java
rename to skill-tree/src/test/java/com/RDS/skilltree/integration/skills/SkillRequestActionIntegrationTest.java
index 1510741c..7ee5b016 100644
--- a/skill-tree/src/test/java/com/RDS/skilltree/skills/SkillRequestActionIntegrationTest.java
+++ b/skill-tree/src/test/java/com/RDS/skilltree/integration/skills/SkillRequestActionIntegrationTest.java
@@ -1,9 +1,10 @@
-package com.RDS.skilltree.skills;
+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;
@@ -17,10 +18,7 @@
import com.RDS.skilltree.viewmodels.RdsUserViewModel;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
+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;
@@ -29,14 +27,14 @@
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.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import utils.WithCustomMockUser;
-@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
-public class SkillRequestActionIntegrationTest {
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
+public class SkillRequestActionIntegrationTest extends TestContainerManager {
@Autowired private MockMvc mockMvc;
@Autowired private UserSkillRepository userSkillRepository;
@Autowired private SkillRepository skillRepository;
@@ -101,8 +99,7 @@ public void approveSkillRequest_validRequest_shouldApproveSkillRequest() throws
MockMvcRequestBuilders.post(baseRoute + "/" + skill.getId() + "/action")
.contentType(MediaType.APPLICATION_JSON)
.content(requestBody))
- .andDo(MockMvcResultHandlers.print())
- .andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.status().is(200))
.andExpect(MockMvcResultMatchers.jsonPath("$.message").value("approved"));
// Verify the status was updated in database
@@ -127,7 +124,7 @@ public void rejectSkillRequest_validRequest_shouldRejectSkillRequest() throws Ex
MockMvcRequestBuilders.post(baseRoute + "/" + skill.getId() + "/action")
.contentType(MediaType.APPLICATION_JSON)
.content(requestBody))
- .andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.status().is(200))
.andExpect(MockMvcResultMatchers.jsonPath("$.message").value("rejected"));
// Verify the status was updated in database
@@ -152,7 +149,7 @@ public void approveSkillRequest_NonExistentSkillId_ShouldFail() throws Exception
MockMvcRequestBuilders.post(baseRoute + "/123/action")
.contentType(MediaType.APPLICATION_JSON)
.content(requestBody))
- .andExpect(MockMvcResultMatchers.status().isNotFound());
+ .andExpect(MockMvcResultMatchers.status().is(404));
}
@Test
@@ -171,7 +168,7 @@ public void approveSkillRequest_NonExistentUserId_ShouldFail() throws Exception
MockMvcRequestBuilders.post(baseRoute + "/" + skill.getId() + "/action")
.contentType(MediaType.APPLICATION_JSON)
.content(requestBody))
- .andExpect(MockMvcResultMatchers.status().isNotFound());
+ .andExpect(MockMvcResultMatchers.status().is(404));
}
@Test
@@ -187,7 +184,7 @@ public void approveSkillRequest_MissingRequiredFields_ShouldFail() throws Except
MockMvcRequestBuilders.post(baseRoute + "/" + skill.getId() + "/action")
.contentType(MediaType.APPLICATION_JSON)
.content(requestBody))
- .andExpect(MockMvcResultMatchers.status().isBadRequest());
+ .andExpect(MockMvcResultMatchers.status().is(400));
}
@Test
@@ -206,6 +203,6 @@ public void approveSkillRequest_NonSuperUser_ShouldFail() throws Exception {
MockMvcRequestBuilders.post(baseRoute + "/" + skill.getId() + "/action")
.contentType(MediaType.APPLICATION_JSON)
.content(requestBody))
- .andExpect(MockMvcResultMatchers.status().isForbidden());
+ .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");
From fab2c173fc573dcf557d08738d0be72cbfd8adc9 Mon Sep 17 00:00:00 2001
From: Shyam Vishwakarma <144812100+Shyam-Vishwakarma@users.noreply.github.com>
Date: Wed, 7 May 2025 00:52:05 +0530
Subject: [PATCH 07/10] test: integration test for GET
/v1/skills/{skillId}/endorsements (#194)
* fix: remove harcoded value from test container
* fix: add integration tests for GET /v1/skills/{id}/endorsements
* fix: refactor the code, remove unused dependence, redundant comments
---
.../resources/application-test.properties | 1 +
.../RDS/skilltree/TestContainerManager.java | 17 +-
.../skills/CreateSkillIntegrationTest.java | 4 +-
.../GetAllSkillRequestIntegrationTest.java | 4 +-
.../skills/GetAllSkillsIntegrationTest.java | 4 +-
...tEndorsementsBySkillIdIntegrationTest.java | 238 ++++++++++++++++++
.../SkillRequestActionIntegrationTest.java | 4 +-
.../src/test/java/utils/TestDataHelper.java | 64 +++++
8 files changed, 328 insertions(+), 8 deletions(-)
create mode 100644 skill-tree/src/test/java/com/RDS/skilltree/integration/skills/GetEndorsementsBySkillIdIntegrationTest.java
create mode 100644 skill-tree/src/test/java/utils/TestDataHelper.java
diff --git a/skill-tree/src/main/resources/application-test.properties b/skill-tree/src/main/resources/application-test.properties
index fd45b40c..104ec521 100644
--- a/skill-tree/src/main/resources/application-test.properties
+++ b/skill-tree/src/main/resources/application-test.properties
@@ -1,3 +1,4 @@
cookieName=rds-session-v2-development
+test.db.mysql-image=mysql:8.1.0
spring.flyway.enabled=true
spring.flyway.locations=classpath:db/migrations
diff --git a/skill-tree/src/test/java/com/RDS/skilltree/TestContainerManager.java b/skill-tree/src/test/java/com/RDS/skilltree/TestContainerManager.java
index e37ab25c..99883b13 100644
--- a/skill-tree/src/test/java/com/RDS/skilltree/TestContainerManager.java
+++ b/skill-tree/src/test/java/com/RDS/skilltree/TestContainerManager.java
@@ -1,12 +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;
-public abstract class TestContainerManager {
- @ServiceConnection static MySQLContainer> mySQLContainer = new MySQLContainer<>("mysql:8.1.0");
+@TestConfiguration
+public class TestContainerManager {
+ @Value("${test.db.mysql-image}")
+ private String MYSQL_IMAGE_NAME;
- static {
- mySQLContainer.start();
+ @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/CreateSkillIntegrationTest.java b/skill-tree/src/test/java/com/RDS/skilltree/integration/skills/CreateSkillIntegrationTest.java
index 941543e5..de7963c5 100644
--- 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
@@ -22,6 +22,7 @@
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;
@@ -33,7 +34,8 @@
@ActiveProfiles("test")
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
-public class CreateSkillIntegrationTest extends TestContainerManager {
+@Import(TestContainerManager.class)
+public class CreateSkillIntegrationTest {
@Autowired private MockMvc mockMvc;
@Autowired private SkillRepository skillRepository;
@MockBean private RdsService rdsService;
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
index ac2686ab..3c57a997 100644
--- 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
@@ -27,6 +27,7 @@
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;
@@ -39,7 +40,8 @@
@ActiveProfiles("test")
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
-public class GetAllSkillRequestIntegrationTest extends TestContainerManager {
+@Import(TestContainerManager.class)
+public class GetAllSkillRequestIntegrationTest {
@Autowired private MockMvc mockMvc;
@Autowired private UserSkillRepository userSkillRepository;
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
index ba475422..2c9bfa30 100644
--- 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
@@ -22,6 +22,7 @@
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;
@@ -33,7 +34,8 @@
@ActiveProfiles("test")
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
-public class GetAllSkillsIntegrationTest extends TestContainerManager {
+@Import(TestContainerManager.class)
+public class GetAllSkillsIntegrationTest {
@Autowired private SkillService skillService;
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
index 7ee5b016..8e406c0e 100644
--- 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
@@ -23,6 +23,7 @@
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;
@@ -34,7 +35,8 @@
@ActiveProfiles("test")
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
-public class SkillRequestActionIntegrationTest extends TestContainerManager {
+@Import(TestContainerManager.class)
+public class SkillRequestActionIntegrationTest {
@Autowired private MockMvc mockMvc;
@Autowired private UserSkillRepository userSkillRepository;
@Autowired private SkillRepository skillRepository;
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();
+ }
+}
From 55c32a2338dd4fc7dc6fc552bcc9d3c34f41b00f Mon Sep 17 00:00:00 2001
From: Mohit Ramani <75924391+mbramani@users.noreply.github.com>
Date: Sun, 11 May 2025 00:24:09 +0530
Subject: [PATCH 08/10] fix: restrict endorsement requests visibility to users
own requests (#196)
* fix: restrict endorsement requests to only those user has endorsed
* test: verify endorsement request privacy controls
* chore: add dev feature flag
* fix: simplify skill request retrieval logic for superusers and non dev feature flag
* fix: optimize user view model retrieval
* test: enhance integration tests
* fix: optimize endorsement retrieval by reducing redundant user detail fetches
* fix: update skill request retrieval logic
* refactor: rename devMode parameter to isDev for clarity in SkillService and SkillsApi
* refactor: remove unused findByEndorseIdAndSkillIdAndEndorserId method from EndorsementRepository
* test: add integration negative tests for skill requests with dev flag scenarios
---------
Co-authored-by: Mayank Bansal
---
.../com/RDS/skilltree/apis/SkillsApi.java | 8 +-
.../repositories/UserSkillRepository.java | 27 ++++++-
.../RDS/skilltree/services/SkillService.java | 4 +-
.../services/SkillServiceImplementation.java | 45 +++++++----
.../GetAllSkillRequestIntegrationTest.java | 74 +++++++++++++++++++
.../test/java/utils/CustomResultMatchers.java | 20 +++++
6 files changed, 156 insertions(+), 22 deletions(-)
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 0515e27e..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")
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..ee45d77d 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
@@ -12,9 +12,28 @@ 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 "
- + "WHERE e.endorserId = :endorserId")
+ @Query("""
+ 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/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/test/java/com/RDS/skilltree/integration/skills/GetAllSkillRequestIntegrationTest.java b/skill-tree/src/test/java/com/RDS/skilltree/integration/skills/GetAllSkillRequestIntegrationTest.java
index 3c57a997..5f13355b 100644
--- 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
@@ -203,6 +203,28 @@ public void getAllRequests_asNormalUser_shouldReturnAllRequestsByEndorser() thro
.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(
@@ -222,6 +244,29 @@ public void getAllRequests_ByStatus_ShouldReturnFilteredRequests() throws Except
.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(
@@ -235,6 +280,20 @@ public void noSkillRequestsEndorsedByUser_ShouldReturnEmptyLists() throws Except
.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(
@@ -250,6 +309,21 @@ public void noMatchingRequestsByStatus_ShouldReturnEmptyLists() throws Exception
.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(
diff --git a/skill-tree/src/test/java/utils/CustomResultMatchers.java b/skill-tree/src/test/java/utils/CustomResultMatchers.java
index 5eb85e6c..2c354a94 100644
--- a/skill-tree/src/test/java/utils/CustomResultMatchers.java
+++ b/skill-tree/src/test/java/utils/CustomResultMatchers.java
@@ -67,6 +67,26 @@ public static ResultMatcher hasUser(String userId, String 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)) {
From afb7a766682dafd4cd952ba96ff389dee611043d Mon Sep 17 00:00:00 2001
From: Shyam Vishwakarma <144812100+Shyam-Vishwakarma@users.noreply.github.com>
Date: Thu, 15 May 2025 23:00:33 +0530
Subject: [PATCH 09/10] fix: avoid creating duplicate endorsements, add
integration-tests for endorsement creation (#198)
* fix: avoid creating duplicate endorsements, add integration tests
* fix: update name of the test
* fix: update formatting
* test: add test to validate skill creation on a diff skill than those that have endorsements
* fix: update query formatting
* fix: update query to scan only endorsement table, as joining user-skills is redundant
* test: add test-case to test flow when user is unauthorized
* fix: update test name, createUrl(), log
* add class to contain constant messages across app
* fix: use constants defined in Constants.java
* fix: use constants defined in Constants.java
* fix: make constructor of Constants private to prevent instantiation
* fix: import ExceptionMessages from Constants
* fix: update status from 409 to 405
* fix: update query, subquery is redundant
* fix: add logging level for each envioronment
* fix: avoid appending timestamps, message etc, update error message
* fix: add constructor for GenericResponse to create obj with message only
---
.../EndorsementAlreadyExistsException.java | 7 +
.../exceptions/GlobalExceptionHandler.java | 98 ++---
.../repositories/EndorsementRepository.java | 14 +
.../EndorsementServiceImplementation.java | 24 +-
.../com/RDS/skilltree/utils/Constants.java | 12 +
.../RDS/skilltree/utils/GenericResponse.java | 4 +
.../application-production.properties | 3 +-
.../resources/application-staging.properties | 3 +-
.../resources/application-test.properties | 1 +
.../src/main/resources/logback-test.xml | 14 -
.../CreateEndorsementIntegrationTest.java | 370 ++++++++++++++++++
11 files changed, 472 insertions(+), 78 deletions(-)
create mode 100644 skill-tree/src/main/java/com/RDS/skilltree/exceptions/EndorsementAlreadyExistsException.java
create mode 100644 skill-tree/src/main/java/com/RDS/skilltree/utils/Constants.java
delete mode 100644 skill-tree/src/main/resources/logback-test.xml
create mode 100644 skill-tree/src/test/java/com/RDS/skilltree/integration/skills/CreateEndorsementIntegrationTest.java
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..1efe5417 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,23 @@
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/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/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
index 104ec521..55ca50d8 100644
--- a/skill-tree/src/main/resources/application-test.properties
+++ b/skill-tree/src/main/resources/application-test.properties
@@ -2,3 +2,4 @@ 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/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);
+ }
+}
From 785af90c73dfbd59bec11b58eab3ffaa1cb05b10 Mon Sep 17 00:00:00 2001
From: Shyam Vishwakarma <144812100+Shyam-Vishwakarma@users.noreply.github.com>
Date: Fri, 16 May 2025 00:45:16 +0530
Subject: [PATCH 10/10] fix: update formatting of queries (#204)
---
.../repositories/EndorsementRepository.java | 11 +++----
.../repositories/UserSkillRepository.java | 32 ++++++++-----------
2 files changed, 17 insertions(+), 26 deletions(-)
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 1efe5417..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
@@ -11,13 +11,10 @@ public interface EndorsementRepository extends JpaRepository 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
- """)
+ @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,
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 ee45d77d..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
@@ -12,28 +12,22 @@ 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
- WHERE e.endorserId = :endorserId
- """)
+ @Query(
+ "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
- """)
+ @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
- """)
+ @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
- );
-
+ @Param("status") UserSkillStatusEnum status, @Param("endorserId") String endorserId);
}