From f34aee38ca78017927ce591ad9c9fe0f0cd75c6f Mon Sep 17 00:00:00 2001 From: f-s-h Date: Tue, 9 Jun 2026 11:46:27 +0200 Subject: [PATCH 01/28] Moved HelloController to controller/HelloController --- .../devoops/memberservice/{ => controller}/HelloController.java | 2 +- .../java/tum/devoops/memberservice/HelloControllerTest.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) rename services/spring-member/src/main/java/tum/devoops/memberservice/{ => controller}/HelloController.java (92%) diff --git a/services/spring-member/src/main/java/tum/devoops/memberservice/HelloController.java b/services/spring-member/src/main/java/tum/devoops/memberservice/controller/HelloController.java similarity index 92% rename from services/spring-member/src/main/java/tum/devoops/memberservice/HelloController.java rename to services/spring-member/src/main/java/tum/devoops/memberservice/controller/HelloController.java index c88392b..c0715a3 100644 --- a/services/spring-member/src/main/java/tum/devoops/memberservice/HelloController.java +++ b/services/spring-member/src/main/java/tum/devoops/memberservice/controller/HelloController.java @@ -1,4 +1,4 @@ -package tum.devoops.memberservice; +package tum.devoops.memberservice.controller; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; diff --git a/services/spring-member/src/test/java/tum/devoops/memberservice/HelloControllerTest.java b/services/spring-member/src/test/java/tum/devoops/memberservice/HelloControllerTest.java index c0efa3e..fe1f45a 100644 --- a/services/spring-member/src/test/java/tum/devoops/memberservice/HelloControllerTest.java +++ b/services/spring-member/src/test/java/tum/devoops/memberservice/HelloControllerTest.java @@ -13,6 +13,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import tum.devoops.memberservice.config.SecurityConfig; +import tum.devoops.memberservice.controller.HelloController; @WebMvcTest(HelloController.class) @Import(SecurityConfig.class) From 54045fa4f377136668a707dcef1fbedf3dcc1b09 Mon Sep 17 00:00:00 2001 From: f-s-h Date: Tue, 9 Jun 2026 11:46:59 +0200 Subject: [PATCH 02/28] Implemented placeholder MemberController and MemberService --- .../memberservice/controller/MemberController.java | 10 ++++++++++ .../memberservice/service/MemberService.java | 14 ++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java create mode 100644 services/spring-member/src/main/java/tum/devoops/memberservice/service/MemberService.java diff --git a/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java b/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java new file mode 100644 index 0000000..8e801fe --- /dev/null +++ b/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java @@ -0,0 +1,10 @@ +package tum.devoops.memberservice.controller; + +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class MemberController { + + +} diff --git a/services/spring-member/src/main/java/tum/devoops/memberservice/service/MemberService.java b/services/spring-member/src/main/java/tum/devoops/memberservice/service/MemberService.java new file mode 100644 index 0000000..2209ac3 --- /dev/null +++ b/services/spring-member/src/main/java/tum/devoops/memberservice/service/MemberService.java @@ -0,0 +1,14 @@ +package tum.devoops.memberservice.service; + +import org.springframework.stereotype.Service; +import tum.devoops.memberservice.model.MemberSummary; + +import java.util.List; + +@Service +public class MemberService { + + public List getAllMembers() { + return List.of(); + } +} From d6f65073e1fed45244c7f274d4c521c10f7709ea Mon Sep 17 00:00:00 2001 From: f-s-h Date: Tue, 9 Jun 2026 11:48:05 +0200 Subject: [PATCH 03/28] Implemented test for getAllMembers --- .../memberservice/model/MemberDTO.java | 4 + .../memberservice/MemberControllerTest.java | 101 ++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 services/spring-member/src/main/java/tum/devoops/memberservice/model/MemberDTO.java create mode 100644 services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java diff --git a/services/spring-member/src/main/java/tum/devoops/memberservice/model/MemberDTO.java b/services/spring-member/src/main/java/tum/devoops/memberservice/model/MemberDTO.java new file mode 100644 index 0000000..b9668e4 --- /dev/null +++ b/services/spring-member/src/main/java/tum/devoops/memberservice/model/MemberDTO.java @@ -0,0 +1,4 @@ +package tum.devoops.memberservice.model; + +public class MemberDTO { +} diff --git a/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java b/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java new file mode 100644 index 0000000..bf126ea --- /dev/null +++ b/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java @@ -0,0 +1,101 @@ +package tum.devoops.memberservice; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithAnonymousUser; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; +import tum.devoops.memberservice.config.SecurityConfig; +import tum.devoops.memberservice.controller.MemberController; +import tum.devoops.memberservice.model.MemberSummary; +import tum.devoops.memberservice.service.MemberService; + +import java.util.List; +import java.util.UUID; + +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@WebMvcTest(MemberController.class) +@Import(SecurityConfig.class) +public class MemberControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockitoBean + private MemberService memberService; + + @Test + @WithMockUser(roles = "member") + void getMembersAllowedForMember() throws Exception { + mockMvc.perform(get("/")) + .andExpect(status().isOk()); + } + + @Test + @WithMockUser(roles = "admin") + void getMembersAllowedForAdmin() throws Exception { + mockMvc.perform(get("/")) + .andExpect(status().isOk()); + } + + @Test + @WithMockUser(roles = "guest") + void getMembersForbiddenForWrongRole() throws Exception { + mockMvc.perform(get("/")) + .andExpect(status().isForbidden()); + } + + @Test + @WithAnonymousUser + void getMembersUnauthorizedForAnonymous() throws Exception { + mockMvc.perform(get("/")) + .andExpect(status().isUnauthorized()); + } + + @Test + @WithMockUser(roles = "member") + void getMembersContentType() throws Exception { + List list = List.of(); + when(memberService.getAllMembers()).thenReturn(list); + + mockMvc.perform(get("/")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)); + } + + @Test + @WithMockUser(roles = "member") + void getMembersEmptyList() throws Exception { + List list = List.of(); + when(memberService.getAllMembers()).thenReturn(list); + + mockMvc.perform(get("/")) + .andExpect(status().isOk()) + .andExpect(content().json("[]")); + } + + @Test + @WithMockUser(roles = "member") + void getMemberNonEmptyList() throws Exception { + MemberSummary alice = new MemberSummary(UUID.randomUUID(), "Alice", "Aberdeen", "alice.aberdeen@example.com"); + MemberSummary bob = new MemberSummary(UUID.randomUUID(), "Bob", "Builder", "bob.the.builder@example.com"); + List list = List.of(alice, bob); + when(memberService.getAllMembers()).thenReturn(list); + + mockMvc.perform(get("/")) + .andExpect(status().isOk()) + .andExpect(content().json(objectMapper.writeValueAsString(list))); + } + +} From 159e569badefc3f3b59c9a664ca7653031898a19 Mon Sep 17 00:00:00 2001 From: f-s-h Date: Tue, 9 Jun 2026 11:52:56 +0200 Subject: [PATCH 04/28] Deleted redundant MemberDTO --- .../main/java/tum/devoops/memberservice/model/MemberDTO.java | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 services/spring-member/src/main/java/tum/devoops/memberservice/model/MemberDTO.java diff --git a/services/spring-member/src/main/java/tum/devoops/memberservice/model/MemberDTO.java b/services/spring-member/src/main/java/tum/devoops/memberservice/model/MemberDTO.java deleted file mode 100644 index b9668e4..0000000 --- a/services/spring-member/src/main/java/tum/devoops/memberservice/model/MemberDTO.java +++ /dev/null @@ -1,4 +0,0 @@ -package tum.devoops.memberservice.model; - -public class MemberDTO { -} From 3b1a3f722ccc8120d823e9c4a6ade5762a588442 Mon Sep 17 00:00:00 2001 From: f-s-h Date: Tue, 9 Jun 2026 11:53:48 +0200 Subject: [PATCH 05/28] Implemented getAllMembers() in MemberController --- .../controller/MemberController.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java b/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java index 8e801fe..a880c99 100644 --- a/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java +++ b/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java @@ -1,10 +1,25 @@ package tum.devoops.memberservice.controller; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; +import tum.devoops.memberservice.model.MemberSummary; +import tum.devoops.memberservice.service.MemberService; + +import java.util.List; @RestController public class MemberController { - + @Autowired + MemberService memberService; + + @PreAuthorize("hasAnyRole('member', 'admin')") + @GetMapping("/") + public ResponseEntity> getAllMembers() { + return ResponseEntity.ok(memberService.getAllMembers()); + } + } From e4ac893694004081f2b7d33b7fa98a1a0be0cf93 Mon Sep 17 00:00:00 2001 From: f-s-h Date: Tue, 9 Jun 2026 14:53:26 +0200 Subject: [PATCH 06/28] Added comments for test cases --- .../tum/devoops/memberservice/MemberControllerTest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java b/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java index bf126ea..616257d 100644 --- a/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java +++ b/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java @@ -35,6 +35,9 @@ public class MemberControllerTest { @MockitoBean private MemberService memberService; + // Test cases for createMember() endpoint + + // Verifies that members are allowed to access the endpoint @Test @WithMockUser(roles = "member") void getMembersAllowedForMember() throws Exception { @@ -42,6 +45,7 @@ void getMembersAllowedForMember() throws Exception { .andExpect(status().isOk()); } + // Verifies that admin are allowed to access the endpoint @Test @WithMockUser(roles = "admin") void getMembersAllowedForAdmin() throws Exception { @@ -49,6 +53,7 @@ void getMembersAllowedForAdmin() throws Exception { .andExpect(status().isOk()); } + // Verifies that a user with a role other than admin and member is not allowed to access the endpoint (401 forbidden) @Test @WithMockUser(roles = "guest") void getMembersForbiddenForWrongRole() throws Exception { @@ -56,6 +61,7 @@ void getMembersForbiddenForWrongRole() throws Exception { .andExpect(status().isForbidden()); } + // Verifies that an anonymous user is not allowed to access the endpoint (403 unauthorized) @Test @WithAnonymousUser void getMembersUnauthorizedForAnonymous() throws Exception { @@ -63,6 +69,7 @@ void getMembersUnauthorizedForAnonymous() throws Exception { .andExpect(status().isUnauthorized()); } + // Verifies that the content type of the response is application/json @Test @WithMockUser(roles = "member") void getMembersContentType() throws Exception { @@ -74,6 +81,7 @@ void getMembersContentType() throws Exception { .andExpect(content().contentType(MediaType.APPLICATION_JSON)); } + // Verifies that the endpoint returns an empty list if there are no members @Test @WithMockUser(roles = "member") void getMembersEmptyList() throws Exception { @@ -85,6 +93,7 @@ void getMembersEmptyList() throws Exception { .andExpect(content().json("[]")); } + // Verifies that the endpoint returns the list of members correctly when the list is not empty @Test @WithMockUser(roles = "member") void getMemberNonEmptyList() throws Exception { From 1fe590b874b7298b31ffc4682fa1a99d675d7749 Mon Sep 17 00:00:00 2001 From: f-s-h Date: Tue, 9 Jun 2026 15:01:58 +0200 Subject: [PATCH 07/28] Added comment describing getAllMembers --- .../memberservice/controller/MemberController.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java b/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java index a880c99..28eb49a 100644 --- a/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java +++ b/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java @@ -16,6 +16,14 @@ public class MemberController { @Autowired MemberService memberService; + /** + * Retrieves all members. + *

+ * This endpoint searches the primary database and returns all members. + * Only the MemberSummary is returned. + *

+ * @return ResponseEntity containing a List of MemberSummary and HTTP 200 + */ @PreAuthorize("hasAnyRole('member', 'admin')") @GetMapping("/") public ResponseEntity> getAllMembers() { From 8e11443a5e42cf9aa460e85b24f8e5ecd23e3fd1 Mon Sep 17 00:00:00 2001 From: f-s-h Date: Tue, 9 Jun 2026 15:32:01 +0200 Subject: [PATCH 08/28] Placeholder for getMemberById --- .../tum/devoops/memberservice/service/MemberService.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/services/spring-member/src/main/java/tum/devoops/memberservice/service/MemberService.java b/services/spring-member/src/main/java/tum/devoops/memberservice/service/MemberService.java index 2209ac3..3a2b7e5 100644 --- a/services/spring-member/src/main/java/tum/devoops/memberservice/service/MemberService.java +++ b/services/spring-member/src/main/java/tum/devoops/memberservice/service/MemberService.java @@ -1,9 +1,12 @@ package tum.devoops.memberservice.service; import org.springframework.stereotype.Service; +import tum.devoops.memberservice.model.Member; import tum.devoops.memberservice.model.MemberSummary; import java.util.List; +import java.util.Optional; +import java.util.UUID; @Service public class MemberService { @@ -11,4 +14,8 @@ public class MemberService { public List getAllMembers() { return List.of(); } + + public Optional getMemberById(UUID id) { + return null; + } } From 2e2f4ca74c8c3a4dc787239e8363724f4e1b94e3 Mon Sep 17 00:00:00 2001 From: f-s-h Date: Tue, 9 Jun 2026 15:32:17 +0200 Subject: [PATCH 09/28] Added tests for getMemberById --- .../memberservice/MemberControllerTest.java | 98 ++++++++++++++++++- 1 file changed, 94 insertions(+), 4 deletions(-) diff --git a/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java b/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java index 616257d..c632405 100644 --- a/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java +++ b/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java @@ -12,10 +12,13 @@ import org.springframework.test.web.servlet.MockMvc; import tum.devoops.memberservice.config.SecurityConfig; import tum.devoops.memberservice.controller.MemberController; +import tum.devoops.memberservice.model.Member; import tum.devoops.memberservice.model.MemberSummary; import tum.devoops.memberservice.service.MemberService; +import java.time.LocalDate; import java.util.List; +import java.util.Optional; import java.util.UUID; import static org.mockito.Mockito.when; @@ -37,7 +40,7 @@ public class MemberControllerTest { // Test cases for createMember() endpoint - // Verifies that members are allowed to access the endpoint + // Verifies that a user with role "member" is allowed to get all members @Test @WithMockUser(roles = "member") void getMembersAllowedForMember() throws Exception { @@ -45,7 +48,7 @@ void getMembersAllowedForMember() throws Exception { .andExpect(status().isOk()); } - // Verifies that admin are allowed to access the endpoint + // Verifies that a user wit role "admin" is allowed to get all members @Test @WithMockUser(roles = "admin") void getMembersAllowedForAdmin() throws Exception { @@ -53,7 +56,7 @@ void getMembersAllowedForAdmin() throws Exception { .andExpect(status().isOk()); } - // Verifies that a user with a role other than admin and member is not allowed to access the endpoint (401 forbidden) + // Verifies that a user with a role other than "admin" and "member" is not allowed to get all members (401 forbidden) @Test @WithMockUser(roles = "guest") void getMembersForbiddenForWrongRole() throws Exception { @@ -61,7 +64,7 @@ void getMembersForbiddenForWrongRole() throws Exception { .andExpect(status().isForbidden()); } - // Verifies that an anonymous user is not allowed to access the endpoint (403 unauthorized) + // Verifies that an anonymous user is not allowed to get all members (403 unauthorized) @Test @WithAnonymousUser void getMembersUnauthorizedForAnonymous() throws Exception { @@ -107,4 +110,91 @@ void getMemberNonEmptyList() throws Exception { .andExpect(content().json(objectMapper.writeValueAsString(list))); } + // Test cases for getMemberById() endpoint + + // Verifies that a user with role "member" is allowed to retrieve a member by ID + @Test + @WithMockUser(roles = "member") + void getMemberByIdAllowedForMember() throws Exception { + UUID randomId = UUID.randomUUID(); + mockMvc.perform(get(String.format("/%s", randomId), UUID.randomUUID())) + .andExpect(status().isOk()); + } + + // Verifies that a user with role "admin" is allowed to retrieve a member by ID + @Test + @WithMockUser(roles = "admin") + void getMemberByIdAllowedForAdmin() throws Exception { + UUID randomId = UUID.randomUUID(); + mockMvc.perform(get(String.format("/%s", randomId), UUID.randomUUID())) + .andExpect(status().isOk()); + } + + // Verifies that a user with a role other than admin and member is not allowed to get a member by ID (403 forbidden) + @Test + @WithMockUser(roles = "guest") + void getMemberByIdForbiddenForWrongRole() throws Exception { + UUID randomId = UUID.randomUUID(); + mockMvc.perform(get(String.format("/%s", randomId), UUID.randomUUID())) + .andExpect(status().isForbidden()); + } + + // Verifies that an anonymous user is not allowed to get a member by ID (401 unauthorized) + @Test + @WithAnonymousUser + void getMemberByIdUnauthorizedForAnonymous() throws Exception { + UUID randomId = UUID.randomUUID(); + mockMvc.perform(get(String.format("/%s", randomId), UUID.randomUUID())) + .andExpect(status().isUnauthorized()); + } + + // Verifies that the content type of the response is application/json + @Test + @WithMockUser(roles = "member") + void getMemberByIdContentType() throws Exception { + UUID randomId = UUID.randomUUID(); + mockMvc.perform(get(String.format("/%s", randomId), UUID.randomUUID())) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)); + } + + // Verifies that the entire member is returned correctly + @Test + @WithMockUser(roles = "member") + void getMemberByIdReturnsCorrectMember() throws Exception { + Member member = new Member( + UUID.randomUUID(), + "firstName", + "lastName", + "email", + LocalDate.now(), + "phoneNumber", + "address", + LocalDate.now(), + "information" + ); + + when(memberService.getMemberById(member.getId())).thenReturn(Optional.of(member)); + + mockMvc.perform(get(String.format("/%s", member.getId()))) + .andExpect(status().isOk()) + .andExpect(content().json(objectMapper.writeValueAsString(member))); + } + + // Verifies that a 404 not found is returned, when no member for the given id is found + void getMemberByIdReturnsNotFound() throws Exception { + UUID randomId = UUID.randomUUID(); + when(memberService.getMemberById(randomId)).thenReturn(Optional.empty()); + + mockMvc.perform(get(String.format("/%s", randomId))) + .andExpect(status().isNotFound()); + } + + // Verifies that an invalid ID returns a 400 bad request + void getMemberByIdReturnsBadRequestForInvalidId() throws Exception { + String invalidUuid = "invalid-uuid-12345"; + + mockMvc.perform(get(String.format("/%s", invalidUuid))) + .andExpect(status().isBadRequest()); + } } From a09d55ddd164fb99c9fe7898528eb23a06126ca1 Mon Sep 17 00:00:00 2001 From: f-s-h Date: Tue, 9 Jun 2026 15:43:07 +0200 Subject: [PATCH 10/28] Removed UUID type test, as SpringBoot automatically returns 400 if the PathVariable is not of type UUID --- .../tum/devoops/memberservice/MemberControllerTest.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java b/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java index c632405..84e6544 100644 --- a/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java +++ b/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java @@ -190,11 +190,4 @@ void getMemberByIdReturnsNotFound() throws Exception { .andExpect(status().isNotFound()); } - // Verifies that an invalid ID returns a 400 bad request - void getMemberByIdReturnsBadRequestForInvalidId() throws Exception { - String invalidUuid = "invalid-uuid-12345"; - - mockMvc.perform(get(String.format("/%s", invalidUuid))) - .andExpect(status().isBadRequest()); - } } From 2f786c1c2d99a181af0a0f83c30ffcf7fab16481 Mon Sep 17 00:00:00 2001 From: f-s-h Date: Tue, 9 Jun 2026 15:53:41 +0200 Subject: [PATCH 11/28] Added @BeforeEach setup that creates a Member used for testing --- .../memberservice/MemberControllerTest.java | 60 ++++++++++++------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java b/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java index 84e6544..7b45d12 100644 --- a/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java +++ b/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java @@ -1,6 +1,7 @@ package tum.devoops.memberservice; import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; @@ -38,12 +39,31 @@ public class MemberControllerTest { @MockitoBean private MemberService memberService; + private Member member; + + // 2. Initialize it inside the @BeforeEach method + @BeforeEach + void setUp() { + member = new Member( + UUID.randomUUID(), + "firstName", + "lastName", + "email", + LocalDate.now(), + "phoneNumber", + "address", + LocalDate.now(), + "information" + ); + } + // Test cases for createMember() endpoint // Verifies that a user with role "member" is allowed to get all members @Test @WithMockUser(roles = "member") void getMembersAllowedForMember() throws Exception { + when(memberService.getAllMembers()).thenReturn(List.of()); mockMvc.perform(get("/")) .andExpect(status().isOk()); } @@ -52,6 +72,7 @@ void getMembersAllowedForMember() throws Exception { @Test @WithMockUser(roles = "admin") void getMembersAllowedForAdmin() throws Exception { + when(memberService.getAllMembers()).thenReturn(List.of()); mockMvc.perform(get("/")) .andExpect(status().isOk()); } @@ -60,6 +81,7 @@ void getMembersAllowedForAdmin() throws Exception { @Test @WithMockUser(roles = "guest") void getMembersForbiddenForWrongRole() throws Exception { + when(memberService.getAllMembers()).thenReturn(List.of()); mockMvc.perform(get("/")) .andExpect(status().isForbidden()); } @@ -68,6 +90,7 @@ void getMembersForbiddenForWrongRole() throws Exception { @Test @WithAnonymousUser void getMembersUnauthorizedForAnonymous() throws Exception { + when(memberService.getAllMembers()).thenReturn(List.of()); mockMvc.perform(get("/")) .andExpect(status().isUnauthorized()); } @@ -116,8 +139,9 @@ void getMemberNonEmptyList() throws Exception { @Test @WithMockUser(roles = "member") void getMemberByIdAllowedForMember() throws Exception { - UUID randomId = UUID.randomUUID(); - mockMvc.perform(get(String.format("/%s", randomId), UUID.randomUUID())) + when(memberService.getMemberById(member.getId())).thenReturn(Optional.of(member)); + + mockMvc.perform(get(String.format("/%s", member.getId()), UUID.randomUUID())) .andExpect(status().isOk()); } @@ -125,8 +149,9 @@ void getMemberByIdAllowedForMember() throws Exception { @Test @WithMockUser(roles = "admin") void getMemberByIdAllowedForAdmin() throws Exception { - UUID randomId = UUID.randomUUID(); - mockMvc.perform(get(String.format("/%s", randomId), UUID.randomUUID())) + when(memberService.getMemberById(member.getId())).thenReturn(Optional.of(member)); + + mockMvc.perform(get(String.format("/%s", member.getId()), UUID.randomUUID())) .andExpect(status().isOk()); } @@ -134,8 +159,9 @@ void getMemberByIdAllowedForAdmin() throws Exception { @Test @WithMockUser(roles = "guest") void getMemberByIdForbiddenForWrongRole() throws Exception { - UUID randomId = UUID.randomUUID(); - mockMvc.perform(get(String.format("/%s", randomId), UUID.randomUUID())) + when(memberService.getMemberById(member.getId())).thenReturn(Optional.of(member)); + + mockMvc.perform(get(String.format("/%s", member.getId()), UUID.randomUUID())) .andExpect(status().isForbidden()); } @@ -143,8 +169,9 @@ void getMemberByIdForbiddenForWrongRole() throws Exception { @Test @WithAnonymousUser void getMemberByIdUnauthorizedForAnonymous() throws Exception { - UUID randomId = UUID.randomUUID(); - mockMvc.perform(get(String.format("/%s", randomId), UUID.randomUUID())) + when(memberService.getMemberById(member.getId())).thenReturn(Optional.of(member)); + + mockMvc.perform(get(String.format("/%s", member.getId()), UUID.randomUUID())) .andExpect(status().isUnauthorized()); } @@ -152,8 +179,9 @@ void getMemberByIdUnauthorizedForAnonymous() throws Exception { @Test @WithMockUser(roles = "member") void getMemberByIdContentType() throws Exception { - UUID randomId = UUID.randomUUID(); - mockMvc.perform(get(String.format("/%s", randomId), UUID.randomUUID())) + when(memberService.getMemberById(member.getId())).thenReturn(Optional.of(member)); + + mockMvc.perform(get(String.format("/%s", member.getId()), UUID.randomUUID())) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)); } @@ -162,18 +190,6 @@ void getMemberByIdContentType() throws Exception { @Test @WithMockUser(roles = "member") void getMemberByIdReturnsCorrectMember() throws Exception { - Member member = new Member( - UUID.randomUUID(), - "firstName", - "lastName", - "email", - LocalDate.now(), - "phoneNumber", - "address", - LocalDate.now(), - "information" - ); - when(memberService.getMemberById(member.getId())).thenReturn(Optional.of(member)); mockMvc.perform(get(String.format("/%s", member.getId()))) From 9298977a77b290ae2f925703a84f3f3600d44450 Mon Sep 17 00:00:00 2001 From: f-s-h Date: Tue, 9 Jun 2026 15:56:13 +0200 Subject: [PATCH 12/28] Implemented getMemberById endpoint --- .../controller/MemberController.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java b/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java index 28eb49a..be41de6 100644 --- a/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java +++ b/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java @@ -4,11 +4,15 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; +import tum.devoops.memberservice.model.Member; import tum.devoops.memberservice.model.MemberSummary; import tum.devoops.memberservice.service.MemberService; import java.util.List; +import java.util.Optional; +import java.util.UUID; @RestController public class MemberController { @@ -30,4 +34,26 @@ public ResponseEntity> getAllMembers() { return ResponseEntity.ok(memberService.getAllMembers()); } + /** + * Retrieves a member given its ID. + *

+ * This endpoint searches the primary database and returns the corresponding member to an ID. + *

+ * @param The unique {@link UUID} of the member. + * @return ResponseEntity containing a List of MemberSummary and HTTP 200. If the member is not found an empty ResponseEntity with HTTP 404 is returned. + */ + @PreAuthorize("hasAnyRole('member', 'admin')") + @GetMapping("/{id}") + public ResponseEntity getMemberById(@PathVariable UUID id) { + Optional memberOptional = memberService.getMemberById(id); + + if (memberOptional.isPresent()) { + Member member = memberOptional.get(); + return ResponseEntity.ok(member); + } + else { + return ResponseEntity.notFound().build(); + } + } + } From 33ec9c07f22f4b776ed80529f0da07b04676b3f4 Mon Sep 17 00:00:00 2001 From: f-s-h Date: Tue, 9 Jun 2026 18:01:00 +0200 Subject: [PATCH 13/28] Implementd createUser in KeycloakService that creates a user in Keycloak with the default role "member" --- .../service/KeycloakService.java | 62 +++++++++++++++++++ .../src/main/resources/application.properties | 3 + 2 files changed, 65 insertions(+) create mode 100644 services/spring-member/src/main/java/tum/devoops/memberservice/service/KeycloakService.java diff --git a/services/spring-member/src/main/java/tum/devoops/memberservice/service/KeycloakService.java b/services/spring-member/src/main/java/tum/devoops/memberservice/service/KeycloakService.java new file mode 100644 index 0000000..0d66135 --- /dev/null +++ b/services/spring-member/src/main/java/tum/devoops/memberservice/service/KeycloakService.java @@ -0,0 +1,62 @@ +package tum.devoops.memberservice.service; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestClient; +import tum.devoops.memberservice.model.MemberCreate; + +import java.net.URI; +import java.util.UUID; + +@Service +public class KeycloakService { + + private final RestClient restClient; + @Value("${keycloak.realm}") + private String realm; + + public KeycloakService(RestClient.Builder restClientBuilder, @Value("${keycloak.base-url}") String baseUrl) { + this.restClient = restClientBuilder.baseUrl(baseUrl).build(); + } + + public UUID createUser(MemberCreate member, String bearerToken) throws IllegalAccessException { + String username = member.getEmail() != null ? member.getEmail() : (member.getFirstName() + member.getLastName()).toLowerCase(); + + UserRepresentation body = new UserRepresentation(username, member.getFirstName(), member.getLastName(), member.getEmail(), true); + + ResponseEntity response; + + try{ + response = restClient.post() + .uri("/admin/realms/{realm}/users", realm) + .header("Authorization", "Bearer " + bearerToken) + .contentType(MediaType.APPLICATION_JSON) + .body(body) + .retrieve() + .toBodilessEntity(); + } catch (HttpClientErrorException.Conflict e) { + throw new IllegalAccessException("A keycloak user with this username/email already exists"); + } catch (HttpClientErrorException.Forbidden e) { + throw new SecurityException("Insufficient permissions to create a keycloak user"); + } + + URI location = response.getHeaders().getLocation(); + + if (location == null) { + throw new IllegalStateException("Keycloak did not return a location header after user creation"); + } + + String path = location.getPath(); + return UUID.fromString(path.substring(path.lastIndexOf("/") + 1)); + } + + private record UserRepresentation( + String username, + String firstName, + String lastName, String + email, boolean enabled + ) {} +} diff --git a/services/spring-member/src/main/resources/application.properties b/services/spring-member/src/main/resources/application.properties index e3f5a62..8ee8d42 100644 --- a/services/spring-member/src/main/resources/application.properties +++ b/services/spring-member/src/main/resources/application.properties @@ -1,4 +1,7 @@ spring.application.name=member-service +keycloak.base-url=http://localhost:8081 +keycloak.realm=devops + spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8081/realms/devops spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://keycloak:8080/realms/devops/protocol/openid-connect/certs From c25a0cc1364be2ea9a17d5cf87f140bac22571a1 Mon Sep 17 00:00:00 2001 From: f-s-h Date: Tue, 9 Jun 2026 18:37:34 +0200 Subject: [PATCH 14/28] Implement createMember --- .../controller/MemberController.java | 34 +++++++- .../memberservice/service/MemberService.java | 27 +++++++ .../memberservice/MemberControllerTest.java | 78 ++++++++++++++++++- 3 files changed, 134 insertions(+), 5 deletions(-) diff --git a/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java b/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java index be41de6..f1314d6 100644 --- a/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java +++ b/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java @@ -3,13 +3,16 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.context.request.RequestContextHolder; import tum.devoops.memberservice.model.Member; +import tum.devoops.memberservice.model.MemberCreate; import tum.devoops.memberservice.model.MemberSummary; import tum.devoops.memberservice.service.MemberService; +import java.net.URI; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -39,7 +42,7 @@ public ResponseEntity> getAllMembers() { *

* This endpoint searches the primary database and returns the corresponding member to an ID. *

- * @param The unique {@link UUID} of the member. + * @param id The unique {@link UUID} of the member. * @return ResponseEntity containing a List of MemberSummary and HTTP 200. If the member is not found an empty ResponseEntity with HTTP 404 is returned. */ @PreAuthorize("hasAnyRole('member', 'admin')") @@ -56,4 +59,27 @@ public ResponseEntity getMemberById(@PathVariable UUID id) { } } + /** + * Creates a member in the member-db and the corresponding user in keycloak + *

+ * This endpoint creates a member and further creates a corresponding user in keycloak using the email of the member as username. + * If the email is null, the username is firstName.lastName + *

+ * @param memberCreate the member without id. + * @return ResponseEntity containing the created member and HTTP 201. If the email or username exists, returns HTTP 400. + */ + @PreAuthorize("hasRole('admin')") + @PostMapping("/") + public ResponseEntity createMember(@RequestBody MemberCreate memberCreate, @AuthenticationPrincipal Jwt jwt) { + Member member; + try{ + member = memberService.createMember(memberCreate, jwt.getTokenValue()); + } + catch (Exception e){ + return ResponseEntity.badRequest().build(); + } + + return ResponseEntity.created(URI.create("/" + member.getId())).body(member); + } + } diff --git a/services/spring-member/src/main/java/tum/devoops/memberservice/service/MemberService.java b/services/spring-member/src/main/java/tum/devoops/memberservice/service/MemberService.java index 3a2b7e5..cfe677c 100644 --- a/services/spring-member/src/main/java/tum/devoops/memberservice/service/MemberService.java +++ b/services/spring-member/src/main/java/tum/devoops/memberservice/service/MemberService.java @@ -1,9 +1,12 @@ package tum.devoops.memberservice.service; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import tum.devoops.memberservice.model.Member; +import tum.devoops.memberservice.model.MemberCreate; import tum.devoops.memberservice.model.MemberSummary; +import java.time.LocalDate; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -11,6 +14,9 @@ @Service public class MemberService { + @Autowired + KeycloakService keycloakService; + public List getAllMembers() { return List.of(); } @@ -18,4 +24,25 @@ public List getAllMembers() { public Optional getMemberById(UUID id) { return null; } + + private Member createMemberFromDTO(MemberCreate memberCreate, UUID id) { + return new Member( + id, + memberCreate.getFirstName(), + memberCreate.getLastName(), + memberCreate.getEmail(), + memberCreate.getBirthday(), + memberCreate.getPhoneNumber(), + memberCreate.getAddress(), + LocalDate.now(), + memberCreate.getInformation() + ); + } + + public Member createMember(MemberCreate memberCreate, String bearerToken) throws IllegalAccessException { + UUID id = keycloakService.createUser(memberCreate, bearerToken); + // TODO Store member in database + return createMemberFromDTO(memberCreate, id); + } + } diff --git a/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java b/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java index 7b45d12..452d3ab 100644 --- a/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java +++ b/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java @@ -7,6 +7,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.test.context.support.WithAnonymousUser; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.bean.override.mockito.MockitoBean; @@ -14,6 +15,7 @@ import tum.devoops.memberservice.config.SecurityConfig; import tum.devoops.memberservice.controller.MemberController; import tum.devoops.memberservice.model.Member; +import tum.devoops.memberservice.model.MemberCreate; import tum.devoops.memberservice.model.MemberSummary; import tum.devoops.memberservice.service.MemberService; @@ -23,7 +25,9 @@ import java.util.UUID; import static org.mockito.Mockito.when; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @WebMvcTest(MemberController.class) @@ -40,6 +44,8 @@ public class MemberControllerTest { private MemberService memberService; private Member member; + private MemberCreate memberCreate; + private String mockToken; // 2. Initialize it inside the @BeforeEach method @BeforeEach @@ -48,13 +54,24 @@ void setUp() { UUID.randomUUID(), "firstName", "lastName", - "email", + "email@email.com", LocalDate.now(), "phoneNumber", "address", LocalDate.now(), "information" ); + + memberCreate = new MemberCreate(); + memberCreate.setFirstName(member.getFirstName()); + memberCreate.setLastName(member.getLastName()); + memberCreate.setEmail(member.getEmail()); + memberCreate.setPhoneNumber(member.getPhoneNumber()); + memberCreate.setAddress(member.getAddress()); + memberCreate.setInformation(member.getInformation()); + memberCreate.setBirthday(member.getBirthday()); + + mockToken = "mock-token"; } // Test cases for createMember() endpoint @@ -198,6 +215,8 @@ void getMemberByIdReturnsCorrectMember() throws Exception { } // Verifies that a 404 not found is returned, when no member for the given id is found + @Test + @WithMockUser(roles = "member") void getMemberByIdReturnsNotFound() throws Exception { UUID randomId = UUID.randomUUID(); when(memberService.getMemberById(randomId)).thenReturn(Optional.empty()); @@ -206,4 +225,61 @@ void getMemberByIdReturnsNotFound() throws Exception { .andExpect(status().isNotFound()); } + // Test cases for createMember() endpoint + + // Verifies that a user with role "admin" can create a member + @Test + void createMemberAllowedForAdmin() throws Exception { + when(memberService.createMember(memberCreate, mockToken)).thenReturn(member); + + mockMvc.perform(post("/") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(memberCreate)) + .with(jwt() + .jwt(j -> j.tokenValue(mockToken)) + .authorities(new SimpleGrantedAuthority("ROLE_admin")) + )) + .andExpect(status().isCreated()) + .andExpect(content().json(objectMapper.writeValueAsString(member))); + + } + + // Verifies that a user with role "member" cannot create a member (403 forbidden) + @Test + @WithMockUser(roles = "member") + void createMemberNotAllowedForAdmin() throws Exception { + mockMvc.perform(post("/") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(memberCreate)) + ) + .andExpect(status().isForbidden()); + } + + // Verifies that an anonymous user cannot create a member (401 unauthorized) + @Test + @WithAnonymousUser + void createMemberNotAllowedForAnonymousUser() throws Exception { + mockMvc.perform(post("/") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(memberCreate)) + ) + .andExpect(status().isUnauthorized()); + } + + // Verifies that 400 (bad request) is returned when service throws an error + @Test + void createMemberServiceThrows() throws Exception { + when(memberService.createMember(memberCreate, mockToken)).thenThrow(new IllegalAccessException()); + + mockMvc.perform(post("/") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(memberCreate)) + .with(jwt() + .jwt(j -> j.tokenValue(mockToken)) + .authorities(new SimpleGrantedAuthority("ROLE_admin")) + )) + .andExpect(status().isBadRequest()); + } + + } From 55f603715c12773147a7c24ef7c81cf98b837034 Mon Sep 17 00:00:00 2001 From: f-s-h Date: Tue, 9 Jun 2026 23:25:05 +0200 Subject: [PATCH 15/28] Implemented updateMember --- .../controller/MemberController.java | 22 +++ .../memberservice/service/MemberService.java | 7 +- .../memberservice/MemberControllerTest.java | 186 ++++++++++++++++-- 3 files changed, 200 insertions(+), 15 deletions(-) diff --git a/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java b/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java index f1314d6..e6d2d1e 100644 --- a/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java +++ b/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java @@ -82,4 +82,26 @@ public ResponseEntity createMember(@RequestBody MemberCreate memberCreat return ResponseEntity.created(URI.create("/" + member.getId())).body(member); } + /** + * Updates a member in the member db and the keycloak db (if email changes) + *

+ * This endpoint updates a member and further updates the corresponding user in keycloak. + *

+ * @param newMember the updated member. + * @return ResponseEntity containing the updated member and HTTP 200. If the member does not exist, return HTTP 404. + */ + @PreAuthorize("hasRole('admin') or hasRole('member') and #newMember.id.toString() == authentication.name") + @PutMapping("/{id}") + public ResponseEntity updateMember(@PathVariable UUID id, @RequestBody Member newMember, @AuthenticationPrincipal Jwt jwt) { + + Optional newMemberOptional = memberService.updateMember(newMember); + + if (newMemberOptional.isPresent()) { + newMember = newMemberOptional.get(); + return ResponseEntity.ok(newMember); + } + else { + return ResponseEntity.notFound().build(); + } + } } diff --git a/services/spring-member/src/main/java/tum/devoops/memberservice/service/MemberService.java b/services/spring-member/src/main/java/tum/devoops/memberservice/service/MemberService.java index cfe677c..b84367c 100644 --- a/services/spring-member/src/main/java/tum/devoops/memberservice/service/MemberService.java +++ b/services/spring-member/src/main/java/tum/devoops/memberservice/service/MemberService.java @@ -22,7 +22,7 @@ public List getAllMembers() { } public Optional getMemberById(UUID id) { - return null; + return Optional.empty(); } private Member createMemberFromDTO(MemberCreate memberCreate, UUID id) { @@ -45,4 +45,9 @@ public Member createMember(MemberCreate memberCreate, String bearerToken) throws return createMemberFromDTO(memberCreate, id); } + public Optional updateMember(Member member) { + // TODO Update email in keycloak + return Optional.empty(); + } + } diff --git a/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java b/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java index 452d3ab..7f152b4 100644 --- a/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java +++ b/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java @@ -26,8 +26,7 @@ import static org.mockito.Mockito.when; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @WebMvcTest(MemberController.class) @@ -46,12 +45,13 @@ public class MemberControllerTest { private Member member; private MemberCreate memberCreate; private String mockToken; + private Member newMember; - // 2. Initialize it inside the @BeforeEach method @BeforeEach void setUp() { - member = new Member( - UUID.randomUUID(), + UUID id = UUID.randomUUID(); + member = new Member( + id, "firstName", "lastName", "email@email.com", @@ -62,7 +62,7 @@ void setUp() { "information" ); - memberCreate = new MemberCreate(); + memberCreate = new MemberCreate(); memberCreate.setFirstName(member.getFirstName()); memberCreate.setLastName(member.getLastName()); memberCreate.setEmail(member.getEmail()); @@ -72,6 +72,18 @@ void setUp() { memberCreate.setBirthday(member.getBirthday()); mockToken = "mock-token"; + + newMember = new Member( + id, + "newFirstName", + "newLastName", + "newemail@email.com", + LocalDate.now(), + "newPhoneNumber", + "newAddress", + LocalDate.now(), + "newInformation" + ); } // Test cases for createMember() endpoint @@ -233,12 +245,12 @@ void createMemberAllowedForAdmin() throws Exception { when(memberService.createMember(memberCreate, mockToken)).thenReturn(member); mockMvc.perform(post("/") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(memberCreate)) - .with(jwt() - .jwt(j -> j.tokenValue(mockToken)) - .authorities(new SimpleGrantedAuthority("ROLE_admin")) - )) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(memberCreate)) + .with(jwt() + .jwt(j -> j.tokenValue(mockToken)) + .authorities(new SimpleGrantedAuthority("ROLE_admin")) + )) .andExpect(status().isCreated()) .andExpect(content().json(objectMapper.writeValueAsString(member))); @@ -249,8 +261,8 @@ void createMemberAllowedForAdmin() throws Exception { @WithMockUser(roles = "member") void createMemberNotAllowedForAdmin() throws Exception { mockMvc.perform(post("/") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(memberCreate)) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(memberCreate)) ) .andExpect(status().isForbidden()); } @@ -281,5 +293,151 @@ void createMemberServiceThrows() throws Exception { .andExpect(status().isBadRequest()); } + // Test for updateMember() endpoint + + // Verifies that a user with role "admin" is allowed to update a member + @Test + @WithMockUser(roles = "admin") + void updateMemberAllowedForAdmin() throws Exception { + when(memberService.updateMember(newMember)).thenReturn(Optional.of(newMember)); + + mockMvc.perform(put(String.format("/%s", member.getId())) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(newMember)) + ) + .andExpect(status().isOk()) + .andExpect(content().json(objectMapper.writeValueAsString(newMember))); + } + + // Verifies that a user wit role "member" is forbidden to update a member that is not himself (401 forbidden) + @Test + void updateMemberNotAllowedForUserOtherId() throws Exception { + UUID randomId = UUID.randomUUID(); + mockMvc.perform(put(String.format("/%s", member.getId())) + .with(jwt() + .jwt(j -> j.subject(randomId.toString())) + .authorities(new SimpleGrantedAuthority("ROLE_member")) + ) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(newMember)) + ) + .andExpect(status().isForbidden()); + } + + // Verifies that a user with role "member" is allowed to update himself + @Test + void updateMemberAllowedForUserSameId() throws Exception { + when(memberService.updateMember(newMember)).thenReturn(Optional.of(newMember)); + + mockMvc.perform(put(String.format("/%s", member.getId())) + .with(jwt() + .jwt(j -> j.subject(member.getId().toString())) + .authorities(new SimpleGrantedAuthority("ROLE_member")) + ) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(newMember)) + ) + .andExpect(status().isOk()); + } + + // Verifies that a user with an undefined role other than "admin" and "member" is not allowed to update a user that is not himself (401 forbidden) + @Test + void updateMemberNotAllowedForUndefinedRoleOtherId() throws Exception { + UUID randomId = UUID.randomUUID(); + mockMvc.perform(put(String.format("/%s", member.getId())) + .with(jwt() + .jwt(j -> j.subject(randomId.toString())) + .authorities(new SimpleGrantedAuthority("ROLE_guest")) + ) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(newMember)) + ) + .andExpect(status().isForbidden()); + } + + // Verifies that a user with an undefined role other than "admin" and "member" is not allowed to update a user that is himself (401 forbidden) + @Test + void updateMemberNotAllowedForUndefinedRoleSameId() throws Exception { + mockMvc.perform(put(String.format("/%s", member.getId())) + .with(jwt() + .jwt(j -> j.subject(member.getId().toString())) + .authorities(new SimpleGrantedAuthority("ROLE_guest")) + ) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(newMember)) + ) + .andExpect(status().isForbidden()); + } + + // Verifies that an anonymous user is not allowed to update a user (403 unauthorized) + @Test + @WithAnonymousUser + void updateMemberUnauthorizedForAnonymous() throws Exception { + mockMvc.perform(put(String.format("/%s", member.getId())) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(newMember)) + ) + .andExpect(status().isUnauthorized()); + } + + // Verifies that the content type of the response is application/json + @Test + @WithMockUser(roles = "admin") + void updateMemberContentType() throws Exception { + when(memberService.updateMember(newMember)).thenReturn(Optional.of(newMember)); + + mockMvc.perform(put(String.format("/%s", member.getId())) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(newMember)) + ) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)); + } + + // Verifies that the endpoint updates the member properly (200 ok) + @Test + void updateMemberCorrectUpdateForUserSameId() throws Exception { + when(memberService.updateMember(newMember)).thenReturn(Optional.of(newMember)); + + mockMvc.perform(put(String.format("/%s", member.getId())) + .with(jwt() + .jwt(j -> j.subject(member.getId().toString())) + .authorities(new SimpleGrantedAuthority("ROLE_member")) + ) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(newMember)) + ) + .andExpect(status().isOk()) + .andExpect(content().json(objectMapper.writeValueAsString(newMember))); + } + + // Verify that a non-existing member cannot be updated by an admin + @Test + @WithMockUser(roles = "admin") + void updateMemberNotFoundNonExistingId() throws Exception { + when(memberService.updateMember(newMember)).thenReturn(Optional.empty()); + + mockMvc.perform(put(String.format("/%s", UUID.randomUUID())) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(newMember)) + ) + .andExpect(status().isNotFound()); + } + + // Verify that an non-existing member cannot updated by a user even if the id is the same as the user + @Test + void updateMemberNotFoundUserSameId() throws Exception { + when(memberService.updateMember(newMember)).thenReturn(Optional.empty()); + + mockMvc.perform(put(String.format("/%s", member.getId())) + .with(jwt() + .jwt(j -> j.subject(member.getId().toString())) + .authorities(new SimpleGrantedAuthority("ROLE_member")) + ) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(newMember)) + ) + .andExpect(status().isNotFound()); + } } From d2d5313abb584b5aeb8ce5bcff73e013a264f855 Mon Sep 17 00:00:00 2001 From: f-s-h Date: Wed, 10 Jun 2026 08:38:43 +0200 Subject: [PATCH 16/28] Implemented getMemberDetails --- .../controller/MemberController.java | 28 ++++- .../memberservice/service/MemberService.java | 6 +- .../memberservice/MemberControllerTest.java | 100 ++++++++++++++++-- 3 files changed, 119 insertions(+), 15 deletions(-) diff --git a/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java b/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java index e6d2d1e..8d7a622 100644 --- a/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java +++ b/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java @@ -43,12 +43,34 @@ public ResponseEntity> getAllMembers() { * This endpoint searches the primary database and returns the corresponding member to an ID. *

* @param id The unique {@link UUID} of the member. - * @return ResponseEntity containing a List of MemberSummary and HTTP 200. If the member is not found an empty ResponseEntity with HTTP 404 is returned. + * @return ResponseEntity containing a MemberSummary and HTTP 200. If the member is not found an empty ResponseEntity with HTTP 404 is returned. */ @PreAuthorize("hasAnyRole('member', 'admin')") @GetMapping("/{id}") - public ResponseEntity getMemberById(@PathVariable UUID id) { - Optional memberOptional = memberService.getMemberById(id); + public ResponseEntity getMemberById(@PathVariable UUID id) { + Optional memberOptional = memberService.getMemberById(id); + + if (memberOptional.isPresent()) { + MemberSummary member = memberOptional.get(); + return ResponseEntity.ok(member); + } + else { + return ResponseEntity.notFound().build(); + } + } + + /** + * Retrieves a member with all details given its ID. + *

+ * This endpoint searches the primary database and returns the corresponding member to an ID. + *

+ * @param id The unique {@link UUID} of the member. + * @return ResponseEntity containing a Member and HTTP 200. If the member is not found an empty ResponseEntity with HTTP 404 is returned. + */ + @PreAuthorize("hasAnyRole('member', 'admin')") + @GetMapping("/{id}/details") + public ResponseEntity getMemberDetailsById(@PathVariable UUID id) { + Optional memberOptional = memberService.getMemberDetailsById(id); if (memberOptional.isPresent()) { Member member = memberOptional.get(); diff --git a/services/spring-member/src/main/java/tum/devoops/memberservice/service/MemberService.java b/services/spring-member/src/main/java/tum/devoops/memberservice/service/MemberService.java index b84367c..fdaced6 100644 --- a/services/spring-member/src/main/java/tum/devoops/memberservice/service/MemberService.java +++ b/services/spring-member/src/main/java/tum/devoops/memberservice/service/MemberService.java @@ -21,7 +21,11 @@ public List getAllMembers() { return List.of(); } - public Optional getMemberById(UUID id) { + public Optional getMemberById(UUID id) { + return Optional.empty(); + } + + public Optional getMemberDetailsById(UUID id) { return Optional.empty(); } diff --git a/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java b/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java index 7f152b4..e55b6d7 100644 --- a/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java +++ b/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java @@ -42,6 +42,8 @@ public class MemberControllerTest { @MockitoBean private MemberService memberService; + MemberSummary memberSummary; + MemberSummary memberSummary1; private Member member; private MemberCreate memberCreate; private String mockToken; @@ -50,6 +52,10 @@ public class MemberControllerTest { @BeforeEach void setUp() { UUID id = UUID.randomUUID(); + + memberSummary = new MemberSummary(id, "Alice", "Aberdeen", "alice.aberdeen@example.com"); + memberSummary1 = new MemberSummary(UUID.randomUUID(), "Bob", "Builder", "bob.the.builder@example.com"); + member = new Member( id, "firstName", @@ -152,9 +158,7 @@ void getMembersEmptyList() throws Exception { @Test @WithMockUser(roles = "member") void getMemberNonEmptyList() throws Exception { - MemberSummary alice = new MemberSummary(UUID.randomUUID(), "Alice", "Aberdeen", "alice.aberdeen@example.com"); - MemberSummary bob = new MemberSummary(UUID.randomUUID(), "Bob", "Builder", "bob.the.builder@example.com"); - List list = List.of(alice, bob); + List list = List.of(memberSummary, memberSummary1); when(memberService.getAllMembers()).thenReturn(list); mockMvc.perform(get("/")) @@ -162,13 +166,88 @@ void getMemberNonEmptyList() throws Exception { .andExpect(content().json(objectMapper.writeValueAsString(list))); } + // Test cases for getMemberByIdDetails() endpoint + + // Verifies that a user with role "member" is allowed to retrieve a member by ID + @Test + @WithMockUser(roles = "member") + void getMemberDetailsByIdAllowedForMember() throws Exception { + when(memberService.getMemberDetailsById(member.getId())).thenReturn(Optional.of(member)); + + mockMvc.perform(get(String.format("/%s/details", member.getId()), UUID.randomUUID())) + .andExpect(status().isOk()); + } + + // Verifies that a user with role "admin" is allowed to retrieve a member by ID + @Test + @WithMockUser(roles = "admin") + void getMemberDetailsByIdAllowedForAdmin() throws Exception { + when(memberService.getMemberDetailsById(member.getId())).thenReturn(Optional.of(member)); + + mockMvc.perform(get(String.format("/%s/details", member.getId()), UUID.randomUUID())) + .andExpect(status().isOk()); + } + + // Verifies that a user with a role other than admin and member is not allowed to get a member by ID (403 forbidden) + @Test + @WithMockUser(roles = "guest") + void getMemberDetailsByIdForbiddenForWrongRole() throws Exception { + when(memberService.getMemberDetailsById(member.getId())).thenReturn(Optional.of(member)); + + mockMvc.perform(get(String.format("/%s/details", member.getId()), UUID.randomUUID())) + .andExpect(status().isForbidden()); + } + + // Verifies that an anonymous user is not allowed to get a member by ID (401 unauthorized) + @Test + @WithAnonymousUser + void getMemberDetailsByIdUnauthorizedForAnonymous() throws Exception { + when(memberService.getMemberDetailsById(member.getId())).thenReturn(Optional.of(member)); + + mockMvc.perform(get(String.format("/%s/details", member.getId()), UUID.randomUUID())) + .andExpect(status().isUnauthorized()); + } + + // Verifies that the content type of the response is application/json + @Test + @WithMockUser(roles = "member") + void getMemberDetailsByIdContentType() throws Exception { + when(memberService.getMemberDetailsById(member.getId())).thenReturn(Optional.of(member)); + + mockMvc.perform(get(String.format("/%s/details", member.getId()), UUID.randomUUID())) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)); + } + + // Verifies that the entire member is returned correctly + @Test + @WithMockUser(roles = "member") + void getMemberDetailsByIdReturnsCorrectMember() throws Exception { + when(memberService.getMemberDetailsById(member.getId())).thenReturn(Optional.of(member)); + + mockMvc.perform(get(String.format("/%s/details", member.getId()))) + .andExpect(status().isOk()) + .andExpect(content().json(objectMapper.writeValueAsString(member))); + } + + // Verifies that a 404 not found is returned, when no member for the given id is found + @Test + @WithMockUser(roles = "member") + void getMemberDetailsByIdReturnsNotFound() throws Exception { + UUID randomId = UUID.randomUUID(); + when(memberService.getMemberById(randomId)).thenReturn(Optional.empty()); + + mockMvc.perform(get(String.format("/%s/details", randomId))) + .andExpect(status().isNotFound()); + } + // Test cases for getMemberById() endpoint // Verifies that a user with role "member" is allowed to retrieve a member by ID @Test @WithMockUser(roles = "member") void getMemberByIdAllowedForMember() throws Exception { - when(memberService.getMemberById(member.getId())).thenReturn(Optional.of(member)); + when(memberService.getMemberById(member.getId())).thenReturn(Optional.of(memberSummary)); mockMvc.perform(get(String.format("/%s", member.getId()), UUID.randomUUID())) .andExpect(status().isOk()); @@ -178,7 +257,7 @@ void getMemberByIdAllowedForMember() throws Exception { @Test @WithMockUser(roles = "admin") void getMemberByIdAllowedForAdmin() throws Exception { - when(memberService.getMemberById(member.getId())).thenReturn(Optional.of(member)); + when(memberService.getMemberById(member.getId())).thenReturn(Optional.of(memberSummary)); mockMvc.perform(get(String.format("/%s", member.getId()), UUID.randomUUID())) .andExpect(status().isOk()); @@ -188,7 +267,7 @@ void getMemberByIdAllowedForAdmin() throws Exception { @Test @WithMockUser(roles = "guest") void getMemberByIdForbiddenForWrongRole() throws Exception { - when(memberService.getMemberById(member.getId())).thenReturn(Optional.of(member)); + when(memberService.getMemberById(member.getId())).thenReturn(Optional.of(memberSummary)); mockMvc.perform(get(String.format("/%s", member.getId()), UUID.randomUUID())) .andExpect(status().isForbidden()); @@ -198,7 +277,7 @@ void getMemberByIdForbiddenForWrongRole() throws Exception { @Test @WithAnonymousUser void getMemberByIdUnauthorizedForAnonymous() throws Exception { - when(memberService.getMemberById(member.getId())).thenReturn(Optional.of(member)); + when(memberService.getMemberById(member.getId())).thenReturn(Optional.of(memberSummary)); mockMvc.perform(get(String.format("/%s", member.getId()), UUID.randomUUID())) .andExpect(status().isUnauthorized()); @@ -208,7 +287,7 @@ void getMemberByIdUnauthorizedForAnonymous() throws Exception { @Test @WithMockUser(roles = "member") void getMemberByIdContentType() throws Exception { - when(memberService.getMemberById(member.getId())).thenReturn(Optional.of(member)); + when(memberService.getMemberById(member.getId())).thenReturn(Optional.of(memberSummary)); mockMvc.perform(get(String.format("/%s", member.getId()), UUID.randomUUID())) .andExpect(status().isOk()) @@ -219,11 +298,11 @@ void getMemberByIdContentType() throws Exception { @Test @WithMockUser(roles = "member") void getMemberByIdReturnsCorrectMember() throws Exception { - when(memberService.getMemberById(member.getId())).thenReturn(Optional.of(member)); + when(memberService.getMemberById(member.getId())).thenReturn(Optional.of(memberSummary)); mockMvc.perform(get(String.format("/%s", member.getId()))) .andExpect(status().isOk()) - .andExpect(content().json(objectMapper.writeValueAsString(member))); + .andExpect(content().json(objectMapper.writeValueAsString(memberSummary))); } // Verifies that a 404 not found is returned, when no member for the given id is found @@ -439,5 +518,4 @@ void updateMemberNotFoundUserSameId() throws Exception { ) .andExpect(status().isNotFound()); } - } From be3c85bae87d984b2e3b25c7aea3527f21b0bf2a Mon Sep 17 00:00:00 2001 From: f-s-h Date: Wed, 10 Jun 2026 08:54:50 +0200 Subject: [PATCH 17/28] Implemented deleteMember --- .../controller/MemberController.java | 22 +++++ .../memberservice/service/MemberService.java | 5 ++ .../memberservice/MemberControllerTest.java | 89 ++++++++++++++++++- 3 files changed, 113 insertions(+), 3 deletions(-) diff --git a/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java b/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java index 8d7a622..11de772 100644 --- a/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java +++ b/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java @@ -126,4 +126,26 @@ public ResponseEntity updateMember(@PathVariable UUID id, @RequestBody M return ResponseEntity.notFound().build(); } } + + /** + * Deletes a member in the member db and the keycloak db + *

+ * This endpoint deletes a member and further deletes the corresponding user in keycloak. + *

+ * @param id the id of the member to be deleted. + * @return Empty response and HTTP 204. If the member does not exist, return HTTP 404. + */ + @PreAuthorize("hasRole('admin')") + @PutMapping("/{id}") + public ResponseEntity updateMember(@PathVariable UUID id) { + + boolean isDeleted = memberService.deleteMember(id); + + if (isDeleted) { + return ResponseEntity.noContent().build(); + } + else { + return ResponseEntity.notFound().build(); + } + } } diff --git a/services/spring-member/src/main/java/tum/devoops/memberservice/service/MemberService.java b/services/spring-member/src/main/java/tum/devoops/memberservice/service/MemberService.java index fdaced6..e2c1fb4 100644 --- a/services/spring-member/src/main/java/tum/devoops/memberservice/service/MemberService.java +++ b/services/spring-member/src/main/java/tum/devoops/memberservice/service/MemberService.java @@ -54,4 +54,9 @@ public Optional updateMember(Member member) { return Optional.empty(); } + public boolean deleteMember(UUID id) { + // TODO Additionally delete user in keycloak + return false; + } + } diff --git a/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java b/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java index e55b6d7..2371ccc 100644 --- a/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java +++ b/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java @@ -419,7 +419,7 @@ void updateMemberAllowedForUserSameId() throws Exception { .andExpect(status().isOk()); } - // Verifies that a user with an undefined role other than "admin" and "member" is not allowed to update a user that is not himself (401 forbidden) + // Verifies that a user with an undefined role other than "admin" and "member" is not allowed to update a member that is not himself (401 forbidden) @Test void updateMemberNotAllowedForUndefinedRoleOtherId() throws Exception { UUID randomId = UUID.randomUUID(); @@ -434,7 +434,7 @@ void updateMemberNotAllowedForUndefinedRoleOtherId() throws Exception { .andExpect(status().isForbidden()); } - // Verifies that a user with an undefined role other than "admin" and "member" is not allowed to update a user that is himself (401 forbidden) + // Verifies that a user with an undefined role other than "admin" and "member" is not allowed to update a member that is himself (401 forbidden) @Test void updateMemberNotAllowedForUndefinedRoleSameId() throws Exception { mockMvc.perform(put(String.format("/%s", member.getId())) @@ -448,7 +448,7 @@ void updateMemberNotAllowedForUndefinedRoleSameId() throws Exception { .andExpect(status().isForbidden()); } - // Verifies that an anonymous user is not allowed to update a user (403 unauthorized) + // Verifies that an anonymous user is not allowed to update a member (403 unauthorized) @Test @WithAnonymousUser void updateMemberUnauthorizedForAnonymous() throws Exception { @@ -518,4 +518,87 @@ void updateMemberNotFoundUserSameId() throws Exception { ) .andExpect(status().isNotFound()); } + + // Test for deleteMember() endpoint + + // Verifies that a user with role "admin" is allowed to delete a member (204 no content) + @Test + @WithMockUser(roles = "admin") + void deleteMemberAllowedForAdmin() throws Exception { + when(memberService.deleteMember(member.getId())).thenReturn(true); + + mockMvc.perform(delete(String.format("/%s", member.getId()))) + .andExpect(status().isNoContent()); + } + + // Verifies that a user with role "member" is not allowed to delete a member that is not himself + @Test + @WithMockUser(roles = "member") + void deleteMemberNotAllowedForUserOtherId() throws Exception { + UUID randomId = UUID.randomUUID(); + mockMvc.perform(delete(String.format("/%s", member.getId())) + .with(jwt() + .jwt(j -> j.subject(randomId.toString())) + .authorities(new SimpleGrantedAuthority("ROLE_member")) + ) + ) + .andExpect(status().isForbidden()); + } + + // Verifies that a user wit role "member" is forbidden to delete a member even though it is himself (401 forbidden) + @Test + void deleteMemberNotAllowedForUserSameId() throws Exception { + mockMvc.perform(put(String.format("/%s", member.getId())) + .with(jwt() + .jwt(j -> j.subject(member.getId().toString())) + .authorities(new SimpleGrantedAuthority("ROLE_member")) + ) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(newMember)) + ) + .andExpect(status().isForbidden()); + } + + // Verifies that a user with an undefined role other than "admin" and "member" is not allowed to delete a member that is not himself (401 forbidden) + @Test + void deleteMemberNotAllowedForUndefinedRoleOtherId() throws Exception { + UUID randomId = UUID.randomUUID(); + mockMvc.perform(delete(String.format("/%s", member.getId())) + .with(jwt() + .jwt(j -> j.subject(randomId.toString())) + .authorities(new SimpleGrantedAuthority("ROLE_guest")) + ) + ) + .andExpect(status().isForbidden()); + } + + // Verifies that a user with an undefined role other than "admin" and "member" is not allowed to delete a member that is himself (401 forbidden) + @Test + void deleteMemberNotAllowedForUndefinedRoleSameId() throws Exception { + mockMvc.perform(put(String.format("/%s", member.getId())) + .with(jwt() + .jwt(j -> j.subject(member.getId().toString())) + .authorities(new SimpleGrantedAuthority("ROLE_guest")) + ) + ) + .andExpect(status().isForbidden()); + } + + // Verifies that an anonymous user is not allowed to delete a member (403 unauthorized) + @Test + @WithAnonymousUser + void deleteMemberUnauthorizedForAnonymous() throws Exception { + mockMvc.perform(delete(String.format("/%s", member.getId()))) + .andExpect(status().isUnauthorized()); + } + + // Verify that a non-existing member cannot be delted by an admin + @Test + @WithMockUser(roles = "admin") + void deleteMemberNotFoundNonExistingId() throws Exception { + when(memberService.deleteMember(member.getId())).thenReturn(false); + + mockMvc.perform(put(String.format("/%s", member.getId()))) + .andExpect(status().isNotFound()); + } } From 4f07cb1a9bb5df7569bfc97f4ced8bb212f6ba68 Mon Sep 17 00:00:00 2001 From: f-s-h Date: Wed, 17 Jun 2026 10:57:46 +0200 Subject: [PATCH 18/28] Renamed from MemberServiceApplicationTests.java to MemberServiceApplicationTest.java --- ...eApplicationTests.java => MemberServiceApplicationTest.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename services/spring-member/src/test/java/tum/devoops/memberservice/{MemberServiceApplicationTests.java => MemberServiceApplicationTest.java} (95%) diff --git a/services/spring-member/src/test/java/tum/devoops/memberservice/MemberServiceApplicationTests.java b/services/spring-member/src/test/java/tum/devoops/memberservice/MemberServiceApplicationTest.java similarity index 95% rename from services/spring-member/src/test/java/tum/devoops/memberservice/MemberServiceApplicationTests.java rename to services/spring-member/src/test/java/tum/devoops/memberservice/MemberServiceApplicationTest.java index 0fc4502..1c526af 100644 --- a/services/spring-member/src/test/java/tum/devoops/memberservice/MemberServiceApplicationTests.java +++ b/services/spring-member/src/test/java/tum/devoops/memberservice/MemberServiceApplicationTest.java @@ -18,7 +18,7 @@ @TestPropertySource(properties = { "spring.jpa.hibernate.ddl-auto=none" }) -class MemberServiceApplicationTests { +class MemberServiceApplicationTest { @Test void contextLoads() { From a5aed0372c08a55119fd03da716b0ce10835d2d0 Mon Sep 17 00:00:00 2001 From: f-s-h Date: Wed, 17 Jun 2026 11:25:44 +0200 Subject: [PATCH 19/28] Renamed getMemberById to getMemberSummaryById and getMemberDetailsById to getMemberById --- .../controller/MemberController.java | 36 +++--- .../memberservice/MemberControllerTest.java | 117 ++++++++++-------- 2 files changed, 85 insertions(+), 68 deletions(-) diff --git a/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java b/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java index 11de772..b78ad0f 100644 --- a/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java +++ b/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java @@ -12,6 +12,7 @@ import tum.devoops.memberservice.model.MemberSummary; import tum.devoops.memberservice.service.MemberService; +import javax.swing.text.html.Option; import java.net.URI; import java.util.List; import java.util.Optional; @@ -47,8 +48,8 @@ public ResponseEntity> getAllMembers() { */ @PreAuthorize("hasAnyRole('member', 'admin')") @GetMapping("/{id}") - public ResponseEntity getMemberById(@PathVariable UUID id) { - Optional memberOptional = memberService.getMemberById(id); + public ResponseEntity getMemberSummaryById(@PathVariable UUID id) { + Optional memberOptional = memberService.getMemberSummaryById(id); if (memberOptional.isPresent()) { MemberSummary member = memberOptional.get(); @@ -69,8 +70,8 @@ public ResponseEntity getMemberById(@PathVariable UUID id) { */ @PreAuthorize("hasAnyRole('member', 'admin')") @GetMapping("/{id}/details") - public ResponseEntity getMemberDetailsById(@PathVariable UUID id) { - Optional memberOptional = memberService.getMemberDetailsById(id); + public ResponseEntity getMemberById(@PathVariable UUID id) { + Optional memberOptional = memberService.getMemberById(id); if (memberOptional.isPresent()) { Member member = memberOptional.get(); @@ -93,14 +94,14 @@ public ResponseEntity getMemberDetailsById(@PathVariable UUID id) { @PreAuthorize("hasRole('admin')") @PostMapping("/") public ResponseEntity createMember(@RequestBody MemberCreate memberCreate, @AuthenticationPrincipal Jwt jwt) { - Member member; - try{ - member = memberService.createMember(memberCreate, jwt.getTokenValue()); - } - catch (Exception e){ + Optional optionalMember = memberService.createMember(memberCreate, jwt.getTokenValue()); + + if (optionalMember.isEmpty()) { return ResponseEntity.badRequest().build(); } + Member member = optionalMember.get(); + return ResponseEntity.created(URI.create("/" + member.getId())).body(member); } @@ -116,15 +117,14 @@ public ResponseEntity createMember(@RequestBody MemberCreate memberCreat @PutMapping("/{id}") public ResponseEntity updateMember(@PathVariable UUID id, @RequestBody Member newMember, @AuthenticationPrincipal Jwt jwt) { - Optional newMemberOptional = memberService.updateMember(newMember); + Optional newMemberOptional = memberService.updateMember(newMember, jwt.getTokenValue()); - if (newMemberOptional.isPresent()) { - newMember = newMemberOptional.get(); - return ResponseEntity.ok(newMember); - } - else { + if (newMemberOptional.isEmpty()) { return ResponseEntity.notFound().build(); } + + newMember = newMemberOptional.get(); + return ResponseEntity.ok(newMember); } /** @@ -136,10 +136,10 @@ public ResponseEntity updateMember(@PathVariable UUID id, @RequestBody M * @return Empty response and HTTP 204. If the member does not exist, return HTTP 404. */ @PreAuthorize("hasRole('admin')") - @PutMapping("/{id}") - public ResponseEntity updateMember(@PathVariable UUID id) { + @DeleteMapping("/{id}") + public ResponseEntity deleteMember(@PathVariable UUID id, @AuthenticationPrincipal Jwt jwt) { - boolean isDeleted = memberService.deleteMember(id); + boolean isDeleted = memberService.deleteMember(id, jwt.getTokenValue()); if (isDeleted) { return ResponseEntity.noContent().build(); diff --git a/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java b/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java index 2371ccc..be395f6 100644 --- a/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java +++ b/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java @@ -24,6 +24,7 @@ import java.util.Optional; import java.util.UUID; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.when; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; @@ -166,13 +167,13 @@ void getMemberNonEmptyList() throws Exception { .andExpect(content().json(objectMapper.writeValueAsString(list))); } - // Test cases for getMemberByIdDetails() endpoint + // Test cases for getMemberById() endpoint // Verifies that a user with role "member" is allowed to retrieve a member by ID @Test @WithMockUser(roles = "member") - void getMemberDetailsByIdAllowedForMember() throws Exception { - when(memberService.getMemberDetailsById(member.getId())).thenReturn(Optional.of(member)); + void getMemberByIdAllowedForMember() throws Exception { + when(memberService.getMemberById(member.getId())).thenReturn(Optional.of(member)); mockMvc.perform(get(String.format("/%s/details", member.getId()), UUID.randomUUID())) .andExpect(status().isOk()); @@ -181,8 +182,8 @@ void getMemberDetailsByIdAllowedForMember() throws Exception { // Verifies that a user with role "admin" is allowed to retrieve a member by ID @Test @WithMockUser(roles = "admin") - void getMemberDetailsByIdAllowedForAdmin() throws Exception { - when(memberService.getMemberDetailsById(member.getId())).thenReturn(Optional.of(member)); + void getMemberByIdAllowedForAdmin() throws Exception { + when(memberService.getMemberById(member.getId())).thenReturn(Optional.of(member)); mockMvc.perform(get(String.format("/%s/details", member.getId()), UUID.randomUUID())) .andExpect(status().isOk()); @@ -191,8 +192,8 @@ void getMemberDetailsByIdAllowedForAdmin() throws Exception { // Verifies that a user with a role other than admin and member is not allowed to get a member by ID (403 forbidden) @Test @WithMockUser(roles = "guest") - void getMemberDetailsByIdForbiddenForWrongRole() throws Exception { - when(memberService.getMemberDetailsById(member.getId())).thenReturn(Optional.of(member)); + void getMemberByIdForbiddenForWrongRole() throws Exception { + when(memberService.getMemberById(member.getId())).thenReturn(Optional.of(member)); mockMvc.perform(get(String.format("/%s/details", member.getId()), UUID.randomUUID())) .andExpect(status().isForbidden()); @@ -201,8 +202,8 @@ void getMemberDetailsByIdForbiddenForWrongRole() throws Exception { // Verifies that an anonymous user is not allowed to get a member by ID (401 unauthorized) @Test @WithAnonymousUser - void getMemberDetailsByIdUnauthorizedForAnonymous() throws Exception { - when(memberService.getMemberDetailsById(member.getId())).thenReturn(Optional.of(member)); + void getMemberByIdUnauthorizedForAnonymous() throws Exception { + when(memberService.getMemberById(member.getId())).thenReturn(Optional.of(member)); mockMvc.perform(get(String.format("/%s/details", member.getId()), UUID.randomUUID())) .andExpect(status().isUnauthorized()); @@ -211,8 +212,8 @@ void getMemberDetailsByIdUnauthorizedForAnonymous() throws Exception { // Verifies that the content type of the response is application/json @Test @WithMockUser(roles = "member") - void getMemberDetailsByIdContentType() throws Exception { - when(memberService.getMemberDetailsById(member.getId())).thenReturn(Optional.of(member)); + void getMemberByIdContentType() throws Exception { + when(memberService.getMemberById(member.getId())).thenReturn(Optional.of(member)); mockMvc.perform(get(String.format("/%s/details", member.getId()), UUID.randomUUID())) .andExpect(status().isOk()) @@ -222,8 +223,8 @@ void getMemberDetailsByIdContentType() throws Exception { // Verifies that the entire member is returned correctly @Test @WithMockUser(roles = "member") - void getMemberDetailsByIdReturnsCorrectMember() throws Exception { - when(memberService.getMemberDetailsById(member.getId())).thenReturn(Optional.of(member)); + void getMemberByIdReturnsCorrectMember() throws Exception { + when(memberService.getMemberById(member.getId())).thenReturn(Optional.of(member)); mockMvc.perform(get(String.format("/%s/details", member.getId()))) .andExpect(status().isOk()) @@ -233,7 +234,7 @@ void getMemberDetailsByIdReturnsCorrectMember() throws Exception { // Verifies that a 404 not found is returned, when no member for the given id is found @Test @WithMockUser(roles = "member") - void getMemberDetailsByIdReturnsNotFound() throws Exception { + void getMemberByIdReturnsNotFound() throws Exception { UUID randomId = UUID.randomUUID(); when(memberService.getMemberById(randomId)).thenReturn(Optional.empty()); @@ -241,13 +242,13 @@ void getMemberDetailsByIdReturnsNotFound() throws Exception { .andExpect(status().isNotFound()); } - // Test cases for getMemberById() endpoint + // Test cases for getMemberSummaryById() endpoint // Verifies that a user with role "member" is allowed to retrieve a member by ID @Test @WithMockUser(roles = "member") - void getMemberByIdAllowedForMember() throws Exception { - when(memberService.getMemberById(member.getId())).thenReturn(Optional.of(memberSummary)); + void getMemberSummaryByIdAllowedForMember() throws Exception { + when(memberService.getMemberSummaryById(member.getId())).thenReturn(Optional.of(memberSummary)); mockMvc.perform(get(String.format("/%s", member.getId()), UUID.randomUUID())) .andExpect(status().isOk()); @@ -256,8 +257,8 @@ void getMemberByIdAllowedForMember() throws Exception { // Verifies that a user with role "admin" is allowed to retrieve a member by ID @Test @WithMockUser(roles = "admin") - void getMemberByIdAllowedForAdmin() throws Exception { - when(memberService.getMemberById(member.getId())).thenReturn(Optional.of(memberSummary)); + void getMemberSummaryByIdAllowedForAdmin() throws Exception { + when(memberService.getMemberSummaryById(member.getId())).thenReturn(Optional.of(memberSummary)); mockMvc.perform(get(String.format("/%s", member.getId()), UUID.randomUUID())) .andExpect(status().isOk()); @@ -266,8 +267,8 @@ void getMemberByIdAllowedForAdmin() throws Exception { // Verifies that a user with a role other than admin and member is not allowed to get a member by ID (403 forbidden) @Test @WithMockUser(roles = "guest") - void getMemberByIdForbiddenForWrongRole() throws Exception { - when(memberService.getMemberById(member.getId())).thenReturn(Optional.of(memberSummary)); + void getMemberSummaryByIdForbiddenForWrongRole() throws Exception { + when(memberService.getMemberSummaryById(member.getId())).thenReturn(Optional.of(memberSummary)); mockMvc.perform(get(String.format("/%s", member.getId()), UUID.randomUUID())) .andExpect(status().isForbidden()); @@ -276,8 +277,8 @@ void getMemberByIdForbiddenForWrongRole() throws Exception { // Verifies that an anonymous user is not allowed to get a member by ID (401 unauthorized) @Test @WithAnonymousUser - void getMemberByIdUnauthorizedForAnonymous() throws Exception { - when(memberService.getMemberById(member.getId())).thenReturn(Optional.of(memberSummary)); + void getMemberSummaryByIdUnauthorizedForAnonymous() throws Exception { + when(memberService.getMemberSummaryById(member.getId())).thenReturn(Optional.of(memberSummary)); mockMvc.perform(get(String.format("/%s", member.getId()), UUID.randomUUID())) .andExpect(status().isUnauthorized()); @@ -286,8 +287,8 @@ void getMemberByIdUnauthorizedForAnonymous() throws Exception { // Verifies that the content type of the response is application/json @Test @WithMockUser(roles = "member") - void getMemberByIdContentType() throws Exception { - when(memberService.getMemberById(member.getId())).thenReturn(Optional.of(memberSummary)); + void getMemberSummaryByIdContentType() throws Exception { + when(memberService.getMemberSummaryById(member.getId())).thenReturn(Optional.of(memberSummary)); mockMvc.perform(get(String.format("/%s", member.getId()), UUID.randomUUID())) .andExpect(status().isOk()) @@ -297,8 +298,8 @@ void getMemberByIdContentType() throws Exception { // Verifies that the entire member is returned correctly @Test @WithMockUser(roles = "member") - void getMemberByIdReturnsCorrectMember() throws Exception { - when(memberService.getMemberById(member.getId())).thenReturn(Optional.of(memberSummary)); + void getMemberSummaryByIdReturnsCorrectMember() throws Exception { + when(memberService.getMemberSummaryById(member.getId())).thenReturn(Optional.of(memberSummary)); mockMvc.perform(get(String.format("/%s", member.getId()))) .andExpect(status().isOk()) @@ -308,7 +309,7 @@ void getMemberByIdReturnsCorrectMember() throws Exception { // Verifies that a 404 not found is returned, when no member for the given id is found @Test @WithMockUser(roles = "member") - void getMemberByIdReturnsNotFound() throws Exception { + void getMemberSummaryByIdReturnsNotFound() throws Exception { UUID randomId = UUID.randomUUID(); when(memberService.getMemberById(randomId)).thenReturn(Optional.empty()); @@ -321,7 +322,7 @@ void getMemberByIdReturnsNotFound() throws Exception { // Verifies that a user with role "admin" can create a member @Test void createMemberAllowedForAdmin() throws Exception { - when(memberService.createMember(memberCreate, mockToken)).thenReturn(member); + when(memberService.createMember(memberCreate, mockToken)).thenReturn(Optional.of(member)); mockMvc.perform(post("/") .contentType(MediaType.APPLICATION_JSON) @@ -357,10 +358,10 @@ void createMemberNotAllowedForAnonymousUser() throws Exception { .andExpect(status().isUnauthorized()); } - // Verifies that 400 (bad request) is returned when service throws an error + // Verifies that 400 (bad request) is returned when cannot create the member @Test void createMemberServiceThrows() throws Exception { - when(memberService.createMember(memberCreate, mockToken)).thenThrow(new IllegalAccessException()); + when(memberService.createMember(memberCreate, mockToken)).thenReturn(Optional.empty()); mockMvc.perform(post("/") .contentType(MediaType.APPLICATION_JSON) @@ -376,11 +377,14 @@ void createMemberServiceThrows() throws Exception { // Verifies that a user with role "admin" is allowed to update a member @Test - @WithMockUser(roles = "admin") void updateMemberAllowedForAdmin() throws Exception { - when(memberService.updateMember(newMember)).thenReturn(Optional.of(newMember)); + when(memberService.updateMember(eq(newMember), anyString())).thenReturn(Optional.of(newMember)); mockMvc.perform(put(String.format("/%s", member.getId())) + .with(jwt() + .jwt(j -> j.subject(newMember.getId().toString())) + .authorities(new SimpleGrantedAuthority("ROLE_admin")) + ) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(newMember)) ) @@ -406,7 +410,7 @@ void updateMemberNotAllowedForUserOtherId() throws Exception { // Verifies that a user with role "member" is allowed to update himself @Test void updateMemberAllowedForUserSameId() throws Exception { - when(memberService.updateMember(newMember)).thenReturn(Optional.of(newMember)); + when(memberService.updateMember(eq(newMember), anyString())).thenReturn(Optional.of(newMember)); mockMvc.perform(put(String.format("/%s", member.getId())) .with(jwt() @@ -461,11 +465,14 @@ void updateMemberUnauthorizedForAnonymous() throws Exception { // Verifies that the content type of the response is application/json @Test - @WithMockUser(roles = "admin") void updateMemberContentType() throws Exception { - when(memberService.updateMember(newMember)).thenReturn(Optional.of(newMember)); + when(memberService.updateMember(eq(newMember), anyString())).thenReturn(Optional.of(newMember)); mockMvc.perform(put(String.format("/%s", member.getId())) + .with(jwt() + .jwt(j -> j.subject(member.getId().toString())) + .authorities(new SimpleGrantedAuthority("ROLE_admin")) + ) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(newMember)) ) @@ -476,7 +483,7 @@ void updateMemberContentType() throws Exception { // Verifies that the endpoint updates the member properly (200 ok) @Test void updateMemberCorrectUpdateForUserSameId() throws Exception { - when(memberService.updateMember(newMember)).thenReturn(Optional.of(newMember)); + when(memberService.updateMember(eq(newMember), anyString())).thenReturn(Optional.of(newMember)); mockMvc.perform(put(String.format("/%s", member.getId())) .with(jwt() @@ -492,11 +499,14 @@ void updateMemberCorrectUpdateForUserSameId() throws Exception { // Verify that a non-existing member cannot be updated by an admin @Test - @WithMockUser(roles = "admin") void updateMemberNotFoundNonExistingId() throws Exception { - when(memberService.updateMember(newMember)).thenReturn(Optional.empty()); + when(memberService.updateMember(eq(newMember), anyString())).thenReturn(Optional.empty()); mockMvc.perform(put(String.format("/%s", UUID.randomUUID())) + .with(jwt() + .jwt(j -> j.subject(newMember.getId().toString())) + .authorities(new SimpleGrantedAuthority("ROLE_admin")) + ) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(newMember)) ) @@ -506,7 +516,7 @@ void updateMemberNotFoundNonExistingId() throws Exception { // Verify that an non-existing member cannot updated by a user even if the id is the same as the user @Test void updateMemberNotFoundUserSameId() throws Exception { - when(memberService.updateMember(newMember)).thenReturn(Optional.empty()); + when(memberService.updateMember(eq(newMember), anyString())).thenReturn(Optional.empty()); mockMvc.perform(put(String.format("/%s", member.getId())) .with(jwt() @@ -523,11 +533,15 @@ void updateMemberNotFoundUserSameId() throws Exception { // Verifies that a user with role "admin" is allowed to delete a member (204 no content) @Test - @WithMockUser(roles = "admin") void deleteMemberAllowedForAdmin() throws Exception { - when(memberService.deleteMember(member.getId())).thenReturn(true); + when(memberService.deleteMember(eq(member.getId()), anyString())).thenReturn(true); - mockMvc.perform(delete(String.format("/%s", member.getId()))) + mockMvc.perform(delete(String.format("/%s", member.getId())) + .with(jwt() + .jwt(j -> j.subject(member.getId().toString())) + .authorities(new SimpleGrantedAuthority("ROLE_admin")) + ) + ) .andExpect(status().isNoContent()); } @@ -548,13 +562,11 @@ void deleteMemberNotAllowedForUserOtherId() throws Exception { // Verifies that a user wit role "member" is forbidden to delete a member even though it is himself (401 forbidden) @Test void deleteMemberNotAllowedForUserSameId() throws Exception { - mockMvc.perform(put(String.format("/%s", member.getId())) + mockMvc.perform(delete(String.format("/%s", member.getId())) .with(jwt() .jwt(j -> j.subject(member.getId().toString())) .authorities(new SimpleGrantedAuthority("ROLE_member")) ) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(newMember)) ) .andExpect(status().isForbidden()); } @@ -575,7 +587,7 @@ void deleteMemberNotAllowedForUndefinedRoleOtherId() throws Exception { // Verifies that a user with an undefined role other than "admin" and "member" is not allowed to delete a member that is himself (401 forbidden) @Test void deleteMemberNotAllowedForUndefinedRoleSameId() throws Exception { - mockMvc.perform(put(String.format("/%s", member.getId())) + mockMvc.perform(delete(String.format("/%s", member.getId())) .with(jwt() .jwt(j -> j.subject(member.getId().toString())) .authorities(new SimpleGrantedAuthority("ROLE_guest")) @@ -592,13 +604,18 @@ void deleteMemberUnauthorizedForAnonymous() throws Exception { .andExpect(status().isUnauthorized()); } - // Verify that a non-existing member cannot be delted by an admin + // Verify that a non-existing member cannot be deleted by an admin @Test @WithMockUser(roles = "admin") void deleteMemberNotFoundNonExistingId() throws Exception { - when(memberService.deleteMember(member.getId())).thenReturn(false); + when(memberService.deleteMember(eq(member.getId()), anyString())).thenReturn(false); - mockMvc.perform(put(String.format("/%s", member.getId()))) + mockMvc.perform(delete(String.format("/%s", member.getId())) + .with(jwt() + .jwt(j -> j.subject(member.getId().toString())) + .authorities(new SimpleGrantedAuthority("ROLE_admin")) + ) + ) .andExpect(status().isNotFound()); } } From 1c4740c7f553c46cf590076e8e4824760bd990ba Mon Sep 17 00:00:00 2001 From: f-s-h Date: Wed, 17 Jun 2026 11:45:56 +0200 Subject: [PATCH 20/28] Implemented MemberConverter --- .../converter/MemberConverter.java | 57 +++++++ .../converter/MemberConverterTest.java | 151 ++++++++++++++++++ 2 files changed, 208 insertions(+) create mode 100644 services/spring-member/src/main/java/tum/devoops/memberservice/converter/MemberConverter.java create mode 100644 services/spring-member/src/test/java/tum/devoops/memberservice/converter/MemberConverterTest.java diff --git a/services/spring-member/src/main/java/tum/devoops/memberservice/converter/MemberConverter.java b/services/spring-member/src/main/java/tum/devoops/memberservice/converter/MemberConverter.java new file mode 100644 index 0000000..863fc99 --- /dev/null +++ b/services/spring-member/src/main/java/tum/devoops/memberservice/converter/MemberConverter.java @@ -0,0 +1,57 @@ +package tum.devoops.memberservice.converter; + +import tum.devoops.memberservice.entity.MemberEntity; +import tum.devoops.memberservice.model.Member; +import tum.devoops.memberservice.model.MemberCreate; +import tum.devoops.memberservice.model.MemberSummary; + +import java.time.LocalDate; +import java.util.UUID; + +public class MemberConverter { + public static Member convertMemberEntityToMember(MemberEntity memberEntity) { + return new Member( + memberEntity.getId(), + memberEntity.getFirstName(), + memberEntity.getLastName(), + memberEntity.getEmail(), + memberEntity.getBirthday(), + memberEntity.getPhoneNumber(), + memberEntity.getAddress(), + memberEntity.getJoiningDate(), + memberEntity.getInformation() + ); + } + + public static MemberEntity convertMemberToMemberEntity(Member member) { + return new MemberEntity( + member.getId(), + member.getFirstName(), + member.getLastName(), + member.getEmail(), + member.getBirthday(), + member.getPhoneNumber(), + member.getAddress(), + member.getJoiningDate(), + member.getInformation() + ); + } + + public static MemberEntity convertMemberCreateToMemberEntity(MemberCreate memberCreate, UUID id) { + return new MemberEntity( + id, + memberCreate.getFirstName(), + memberCreate.getLastName(), + memberCreate.getEmail(), + memberCreate.getBirthday(), + memberCreate.getPhoneNumber(), + memberCreate.getAddress(), + LocalDate.now(), + memberCreate.getInformation() + ); + } + + public static MemberSummary convertMemberEntityToMemberSummary(MemberEntity memberEntity) { + return new MemberSummary(memberEntity.getId(), memberEntity.getFirstName(), memberEntity.getLastName(), memberEntity.getEmail()); + } +} diff --git a/services/spring-member/src/test/java/tum/devoops/memberservice/converter/MemberConverterTest.java b/services/spring-member/src/test/java/tum/devoops/memberservice/converter/MemberConverterTest.java new file mode 100644 index 0000000..c2dc632 --- /dev/null +++ b/services/spring-member/src/test/java/tum/devoops/memberservice/converter/MemberConverterTest.java @@ -0,0 +1,151 @@ +package tum.devoops.memberservice.converter; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import tum.devoops.memberservice.entity.MemberEntity; +import tum.devoops.memberservice.model.Member; +import tum.devoops.memberservice.model.MemberCreate; +import tum.devoops.memberservice.model.MemberSummary; + +import java.time.LocalDate; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class MemberConverterTest { + + UUID id; + + private MemberEntity memberEntity; + private Member member; + private MemberCreate memberCreate; + + @BeforeEach + void setUp() { + id = UUID.randomUUID(); + LocalDate birthday = LocalDate.of(1990, 1, 1); + LocalDate joiningDate = LocalDate.of(2020, 6, 15); + + memberEntity = new MemberEntity( + id, + "firstName", + "lastName", + "email@email.com", + birthday, + "phoneNumber", + "address", + joiningDate, + "information" + ); + + member = new Member( + id, + "firstName", + "lastName", + "email@email.com", + birthday, + "phoneNumber", + "address", + joiningDate, + "information" + ); + + memberCreate = new MemberCreate(); + memberCreate.setFirstName("firstName"); + memberCreate.setLastName("lastName"); + memberCreate.setEmail("email@email.com"); + memberCreate.setBirthday(birthday); + memberCreate.setPhoneNumber("phoneNumber"); + memberCreate.setAddress("address"); + memberCreate.setInformation("information"); + } + + // Verifies that every field of a MemberEntity is mapped onto the resulting Member + @Test + void convertMemberEntityToMemberMapsAllFields() { + Member result = MemberConverter.convertMemberEntityToMember(memberEntity); + + assertEquals(id, result.getId()); + assertEquals(memberEntity.getFirstName(), result.getFirstName()); + assertEquals(memberEntity.getLastName(), result.getLastName()); + assertEquals(memberEntity.getEmail(), result.getEmail()); + assertEquals(memberEntity.getBirthday(), result.getBirthday()); + assertEquals(memberEntity.getPhoneNumber(), result.getPhoneNumber()); + assertEquals(memberEntity.getAddress(), result.getAddress()); + assertEquals(memberEntity.getJoiningDate(), result.getJoiningDate()); + assertEquals(memberEntity.getInformation(), result.getInformation()); + } + + // Verifies that every field of a Member is mapped onto the resulting MemberEntity + @Test + void convertMemberToMemberEntityMapsAllFields() { + MemberEntity result = MemberConverter.convertMemberToMemberEntity(member); + + assertEquals(member.getId(), result.getId()); + assertEquals(member.getFirstName(), result.getFirstName()); + assertEquals(member.getLastName(), result.getLastName()); + assertEquals(member.getEmail(), result.getEmail()); + assertEquals(member.getBirthday(), result.getBirthday()); + assertEquals(member.getPhoneNumber(), result.getPhoneNumber()); + assertEquals(member.getAddress(), result.getAddress()); + assertEquals(member.getJoiningDate(), result.getJoiningDate()); + assertEquals(member.getInformation(), result.getInformation()); + } + + // Verifies that the provided id is used, the MemberCreate fields are copied and joiningDate is set to today + @Test + void convertMemberCreateToMemberEntityMapsFieldsAndSetsJoiningDate() { + LocalDate before = LocalDate.now(); + MemberEntity result = MemberConverter.convertMemberCreateToMemberEntity(memberCreate, id); + LocalDate after = LocalDate.now(); + + assertEquals(id, result.getId()); + assertEquals(memberCreate.getFirstName(), result.getFirstName()); + assertEquals(memberCreate.getLastName(), result.getLastName()); + assertEquals(memberCreate.getEmail(), result.getEmail()); + assertEquals(memberCreate.getBirthday(), result.getBirthday()); + assertEquals(memberCreate.getPhoneNumber(), result.getPhoneNumber()); + assertEquals(memberCreate.getAddress(), result.getAddress()); + assertEquals(memberCreate.getInformation(), result.getInformation()); + + // joiningDate is overridden with the current date rather than taken from the input + assertTrue(!result.getJoiningDate().isBefore(before) && !result.getJoiningDate().isAfter(after)); + } + + // Verifies that only id, firstName, lastName and email are mapped onto the summary + @Test + void convertMemberEntityToMemberSummaryMapsSummaryFields() { + MemberSummary result = MemberConverter.convertMemberEntityToMemberSummary(memberEntity); + + assertEquals(id, result.getId()); + assertEquals(memberEntity.getFirstName(), result.getFirstName()); + assertEquals(memberEntity.getLastName(), result.getLastName()); + assertEquals(memberEntity.getEmail(), result.getEmail()); + } + + // Verifies that null optional fields are preserved through the conversion + @Test + void convertMemberEntityToMemberPreservesNullOptionalFields() { + MemberEntity entity = new MemberEntity( + id, + "firstName", + "lastName", + "email@email.com", + null, + null, + null, + null, + null + ); + + Member result = MemberConverter.convertMemberEntityToMember(entity); + + assertNull(result.getBirthday()); + assertNull(result.getPhoneNumber()); + assertNull(result.getAddress()); + assertNull(result.getJoiningDate()); + assertNull(result.getInformation()); + } +} From 8f62244bf7cc592221eea26d216d5d5b3aefb72c Mon Sep 17 00:00:00 2001 From: f-s-h Date: Wed, 17 Jun 2026 11:46:07 +0200 Subject: [PATCH 21/28] Moved Tests to subfolder --- .../memberservice/{ => controller}/HelloControllerTest.java | 3 +-- .../memberservice/{ => controller}/MemberControllerTest.java | 3 +-- .../memberservice/{ => service}/SecurityConfigTest.java | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) rename services/spring-member/src/test/java/tum/devoops/memberservice/{ => controller}/HelloControllerTest.java (96%) rename services/spring-member/src/test/java/tum/devoops/memberservice/{ => controller}/MemberControllerTest.java (99%) rename services/spring-member/src/test/java/tum/devoops/memberservice/{ => service}/SecurityConfigTest.java (98%) diff --git a/services/spring-member/src/test/java/tum/devoops/memberservice/HelloControllerTest.java b/services/spring-member/src/test/java/tum/devoops/memberservice/controller/HelloControllerTest.java similarity index 96% rename from services/spring-member/src/test/java/tum/devoops/memberservice/HelloControllerTest.java rename to services/spring-member/src/test/java/tum/devoops/memberservice/controller/HelloControllerTest.java index fe1f45a..358f358 100644 --- a/services/spring-member/src/test/java/tum/devoops/memberservice/HelloControllerTest.java +++ b/services/spring-member/src/test/java/tum/devoops/memberservice/controller/HelloControllerTest.java @@ -1,4 +1,4 @@ -package tum.devoops.memberservice; +package tum.devoops.memberservice.controller; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -13,7 +13,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import tum.devoops.memberservice.config.SecurityConfig; -import tum.devoops.memberservice.controller.HelloController; @WebMvcTest(HelloController.class) @Import(SecurityConfig.class) diff --git a/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java b/services/spring-member/src/test/java/tum/devoops/memberservice/controller/MemberControllerTest.java similarity index 99% rename from services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java rename to services/spring-member/src/test/java/tum/devoops/memberservice/controller/MemberControllerTest.java index be395f6..3aaf727 100644 --- a/services/spring-member/src/test/java/tum/devoops/memberservice/MemberControllerTest.java +++ b/services/spring-member/src/test/java/tum/devoops/memberservice/controller/MemberControllerTest.java @@ -1,4 +1,4 @@ -package tum.devoops.memberservice; +package tum.devoops.memberservice.controller; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.BeforeEach; @@ -13,7 +13,6 @@ import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; import tum.devoops.memberservice.config.SecurityConfig; -import tum.devoops.memberservice.controller.MemberController; import tum.devoops.memberservice.model.Member; import tum.devoops.memberservice.model.MemberCreate; import tum.devoops.memberservice.model.MemberSummary; diff --git a/services/spring-member/src/test/java/tum/devoops/memberservice/SecurityConfigTest.java b/services/spring-member/src/test/java/tum/devoops/memberservice/service/SecurityConfigTest.java similarity index 98% rename from services/spring-member/src/test/java/tum/devoops/memberservice/SecurityConfigTest.java rename to services/spring-member/src/test/java/tum/devoops/memberservice/service/SecurityConfigTest.java index 48c6216..bb67044 100644 --- a/services/spring-member/src/test/java/tum/devoops/memberservice/SecurityConfigTest.java +++ b/services/spring-member/src/test/java/tum/devoops/memberservice/service/SecurityConfigTest.java @@ -1,4 +1,4 @@ -package tum.devoops.memberservice; +package tum.devoops.memberservice.service; import java.util.List; import java.util.Map; From e8320f8154c04880634d8cf131b3183f47acfc26 Mon Sep 17 00:00:00 2001 From: f-s-h Date: Wed, 17 Jun 2026 11:46:21 +0200 Subject: [PATCH 22/28] Removed unnecessary Test --- .../MemberServiceApplicationTest.java | 27 ------------------- 1 file changed, 27 deletions(-) delete mode 100644 services/spring-member/src/test/java/tum/devoops/memberservice/MemberServiceApplicationTest.java diff --git a/services/spring-member/src/test/java/tum/devoops/memberservice/MemberServiceApplicationTest.java b/services/spring-member/src/test/java/tum/devoops/memberservice/MemberServiceApplicationTest.java deleted file mode 100644 index 1c526af..0000000 --- a/services/spring-member/src/test/java/tum/devoops/memberservice/MemberServiceApplicationTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package tum.devoops.memberservice; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.TestPropertySource; - -/** - * Context-load smoke test. - * - * DataSource and JPA auto-configurations are excluded so the test can run - * without a live PostgreSQL instance. - */ -@SpringBootTest(properties = { - "spring.autoconfigure.exclude=" + - "org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration," + - "org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration" -}) -@TestPropertySource(properties = { - "spring.jpa.hibernate.ddl-auto=none" -}) -class MemberServiceApplicationTest { - - @Test - void contextLoads() { - } - -} From 4c6421c2aa6f6d4835c2c89426262655e13ab990 Mon Sep 17 00:00:00 2001 From: f-s-h Date: Wed, 17 Jun 2026 11:46:38 +0200 Subject: [PATCH 23/28] Added AllArgsConstructor --- .../java/tum/devoops/memberservice/entity/MemberEntity.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/spring-member/src/main/java/tum/devoops/memberservice/entity/MemberEntity.java b/services/spring-member/src/main/java/tum/devoops/memberservice/entity/MemberEntity.java index 54233e6..6a057f3 100644 --- a/services/spring-member/src/main/java/tum/devoops/memberservice/entity/MemberEntity.java +++ b/services/spring-member/src/main/java/tum/devoops/memberservice/entity/MemberEntity.java @@ -9,13 +9,14 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; +import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Entity @Table(schema = "member", name = "members") -@Getter @Setter @NoArgsConstructor +@Getter @Setter @NoArgsConstructor @AllArgsConstructor public class MemberEntity { @Id From 895e9f87eaeb38bb869b2c60c90bc034272cb6ee Mon Sep 17 00:00:00 2001 From: f-s-h Date: Wed, 17 Jun 2026 11:56:23 +0200 Subject: [PATCH 24/28] Implemented KeycloakService --- .../service/KeycloakService.java | 38 +++- .../service/KeycloakServiceTest.java | 169 ++++++++++++++++++ 2 files changed, 205 insertions(+), 2 deletions(-) create mode 100644 services/spring-member/src/test/java/tum/devoops/memberservice/service/KeycloakServiceTest.java diff --git a/services/spring-member/src/main/java/tum/devoops/memberservice/service/KeycloakService.java b/services/spring-member/src/main/java/tum/devoops/memberservice/service/KeycloakService.java index 0d66135..c07b01d 100644 --- a/services/spring-member/src/main/java/tum/devoops/memberservice/service/KeycloakService.java +++ b/services/spring-member/src/main/java/tum/devoops/memberservice/service/KeycloakService.java @@ -6,6 +6,7 @@ import org.springframework.stereotype.Service; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestClient; +import tum.devoops.memberservice.model.Member; import tum.devoops.memberservice.model.MemberCreate; import java.net.URI; @@ -22,14 +23,14 @@ public KeycloakService(RestClient.Builder restClientBuilder, @Value("${keycloak. this.restClient = restClientBuilder.baseUrl(baseUrl).build(); } - public UUID createUser(MemberCreate member, String bearerToken) throws IllegalAccessException { + public UUID createUser(MemberCreate member, String bearerToken) throws Exception { String username = member.getEmail() != null ? member.getEmail() : (member.getFirstName() + member.getLastName()).toLowerCase(); UserRepresentation body = new UserRepresentation(username, member.getFirstName(), member.getLastName(), member.getEmail(), true); ResponseEntity response; - try{ + try { response = restClient.post() .uri("/admin/realms/{realm}/users", realm) .header("Authorization", "Bearer " + bearerToken) @@ -53,6 +54,39 @@ public UUID createUser(MemberCreate member, String bearerToken) throws IllegalAc return UUID.fromString(path.substring(path.lastIndexOf("/") + 1)); } + public void updateUser(Member member, String bearerToken) throws HttpClientErrorException{ + + UserRepresentation body = new UserRepresentation(member.getEmail(), member.getFirstName(), member.getLastName(), member.getEmail() ,true); + + try { + restClient.put() + .uri("/admin/realms/{realm}/users/{id}", realm, member.getId()) + .header("Authorization", "Bearer " + bearerToken) + .contentType(MediaType.APPLICATION_JSON) + .body(body) + .retrieve() + .toBodilessEntity(); + } catch (HttpClientErrorException.Conflict e) { + throw new IllegalArgumentException("A Keycloak user with this email already exists"); + } catch (HttpClientErrorException.Forbidden e) { + throw new SecurityException("Insufficient permissions to update this Keycloak user"); + } + } + + public void deleteUser(UUID keycloakId, String bearerToken) throws HttpClientErrorException, SecurityException{ + try { + restClient.delete() + .uri("/admin/realms/{realm}/users/{id}", realm, keycloakId) + .header("Authorization", "Bearer " + bearerToken) + .retrieve() + .toBodilessEntity(); + } catch (HttpClientErrorException.NotFound e) { + throw new IllegalArgumentException("Keycloak user not found: " + keycloakId); + } catch (HttpClientErrorException.Forbidden e) { + throw new SecurityException("Insufficient permissions to delete a keycloak user"); + } + } + private record UserRepresentation( String username, String firstName, diff --git a/services/spring-member/src/test/java/tum/devoops/memberservice/service/KeycloakServiceTest.java b/services/spring-member/src/test/java/tum/devoops/memberservice/service/KeycloakServiceTest.java new file mode 100644 index 0000000..5444e7c --- /dev/null +++ b/services/spring-member/src/test/java/tum/devoops/memberservice/service/KeycloakServiceTest.java @@ -0,0 +1,169 @@ +package tum.devoops.memberservice.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.client.RestClient; +import tum.devoops.memberservice.model.Member; +import tum.devoops.memberservice.model.MemberCreate; + +import java.time.LocalDate; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus; + +class KeycloakServiceTest { + + private static final String BASE_URL = "http://keycloak.test"; + private static final String REALM = "test-realm"; + private static final String TOKEN = "mock-token"; + private static final String USERS_URI = BASE_URL + "/admin/realms/" + REALM + "/users"; + + private MockRestServiceServer server; + private KeycloakService keycloakService; + + private UUID id; + private MemberCreate memberCreate; + private Member member; + + @BeforeEach + void setUp() { + RestClient.Builder builder = RestClient.builder(); + server = MockRestServiceServer.bindTo(builder).build(); + + keycloakService = new KeycloakService(builder, BASE_URL); + ReflectionTestUtils.setField(keycloakService, "realm", REALM); + + id = UUID.randomUUID(); + + memberCreate = new MemberCreate(); + memberCreate.setFirstName("firstName"); + memberCreate.setLastName("lastName"); + memberCreate.setEmail("email@email.com"); + + member = new Member( + id, + "firstName", + "lastName", + "email@email.com", + LocalDate.of(1990, 1, 1), + "phoneNumber", + "address", + LocalDate.of(2020, 6, 15), + "information" + ); + } + + // Verifies that a successful creation returns the id parsed from the Location header + @Test + void createUserReturnsIdFromLocationHeader() throws Exception { + server.expect(requestTo(USERS_URI)) + .andRespond(withStatus(HttpStatus.CREATED) + .header(HttpHeaders.LOCATION, USERS_URI + "/" + id)); + + UUID result = keycloakService.createUser(memberCreate, TOKEN); + + assertEquals(id, result); + } + + // Verifies that creation succeeds when no email is set (username falls back to the member's name) + @Test + void createUserWithoutEmailReturnsId() throws Exception { + memberCreate.setEmail(null); + + server.expect(requestTo(USERS_URI)) + .andRespond(withStatus(HttpStatus.CREATED) + .header(HttpHeaders.LOCATION, USERS_URI + "/" + id)); + + UUID result = keycloakService.createUser(memberCreate, TOKEN); + + assertEquals(id, result); + } + + // Verifies that a 409 conflict is translated into an IllegalAccessException + @Test + void createUserThrowsOnConflict() { + server.expect(requestTo(USERS_URI)) + .andRespond(withStatus(HttpStatus.CONFLICT)); + + assertThrows(IllegalAccessException.class, () -> keycloakService.createUser(memberCreate, TOKEN)); + } + + // Verifies that a 403 forbidden is translated into a SecurityException + @Test + void createUserThrowsOnForbidden() { + server.expect(requestTo(USERS_URI)) + .andRespond(withStatus(HttpStatus.FORBIDDEN)); + + assertThrows(SecurityException.class, () -> keycloakService.createUser(memberCreate, TOKEN)); + } + + // Verifies that a creation without a Location header fails with an IllegalStateException + @Test + void createUserThrowsWhenNoLocationHeader() { + server.expect(requestTo(USERS_URI)) + .andRespond(withStatus(HttpStatus.CREATED)); + + assertThrows(IllegalStateException.class, () -> keycloakService.createUser(memberCreate, TOKEN)); + } + + // Verifies that a successful update completes without throwing + @Test + void updateUserSucceeds() { + server.expect(requestTo(USERS_URI + "/" + id)) + .andRespond(withStatus(HttpStatus.NO_CONTENT)); + + keycloakService.updateUser(member, TOKEN); + } + + // Verifies that a 409 conflict is translated into an IllegalArgumentException + @Test + void updateUserThrowsOnConflict() { + server.expect(requestTo(USERS_URI + "/" + id)) + .andRespond(withStatus(HttpStatus.CONFLICT)); + + assertThrows(IllegalArgumentException.class, () -> keycloakService.updateUser(member, TOKEN)); + } + + // Verifies that a 403 forbidden is translated into a SecurityException + @Test + void updateUserThrowsOnForbidden() { + server.expect(requestTo(USERS_URI + "/" + id)) + .andRespond(withStatus(HttpStatus.FORBIDDEN)); + + assertThrows(SecurityException.class, () -> keycloakService.updateUser(member, TOKEN)); + } + + // Verifies that a successful deletion completes without throwing + @Test + void deleteUserSucceeds() { + server.expect(requestTo(USERS_URI + "/" + id)) + .andRespond(withStatus(HttpStatus.NO_CONTENT)); + + keycloakService.deleteUser(id, TOKEN); + } + + // Verifies that a 404 not found is translated into an IllegalArgumentException + @Test + void deleteUserThrowsOnNotFound() { + server.expect(requestTo(USERS_URI + "/" + id)) + .andRespond(withStatus(HttpStatus.NOT_FOUND)); + + assertThrows(IllegalArgumentException.class, () -> keycloakService.deleteUser(id, TOKEN)); + } + + // Verifies that a 403 forbidden is translated into a SecurityException + @Test + void deleteUserThrowsOnForbidden() { + server.expect(requestTo(USERS_URI + "/" + id)) + .andRespond(withStatus(HttpStatus.FORBIDDEN)); + + assertThrows(SecurityException.class, () -> keycloakService.deleteUser(id, TOKEN)); + } +} From 66e1a94758c162d8172c02f275ef28723824a11d Mon Sep 17 00:00:00 2001 From: f-s-h Date: Fri, 19 Jun 2026 13:57:11 +0200 Subject: [PATCH 25/28] Implemented equals --- .../devoops/memberservice/entity/MemberEntity.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/services/spring-member/src/main/java/tum/devoops/memberservice/entity/MemberEntity.java b/services/spring-member/src/main/java/tum/devoops/memberservice/entity/MemberEntity.java index 6a057f3..1b47743 100644 --- a/services/spring-member/src/main/java/tum/devoops/memberservice/entity/MemberEntity.java +++ b/services/spring-member/src/main/java/tum/devoops/memberservice/entity/MemberEntity.java @@ -47,4 +47,18 @@ public class MemberEntity { @Column(name = "information", nullable = true, columnDefinition = "TEXT") private String information; + + @Override + public boolean equals(Object o) { + if(!(o instanceof MemberEntity)) { + return false; + } + MemberEntity other = (MemberEntity) o; + + return id.equals(other.getId()) && firstName.equals(other.getFirstName()) + && lastName.equals(other.getLastName()) && email.equals(other.getEmail()) + && birthday.equals(other.getBirthday()) && phoneNumber.equals(other.getPhoneNumber()) + && address.equals(other.getAddress()) && joiningDate.equals(other.getJoiningDate()) + && information.equals(other.getInformation()); + } } From bdc395f196c64b3d9e24e2d5d4861a11129df8af Mon Sep 17 00:00:00 2001 From: f-s-h Date: Fri, 19 Jun 2026 13:57:37 +0200 Subject: [PATCH 26/28] Implemented MemberService --- .../memberservice/service/MemberService.java | 125 ++++++-- .../service/MemberServiceTest.java | 297 ++++++++++++++++++ 2 files changed, 393 insertions(+), 29 deletions(-) create mode 100644 services/spring-member/src/test/java/tum/devoops/memberservice/service/MemberServiceTest.java diff --git a/services/spring-member/src/main/java/tum/devoops/memberservice/service/MemberService.java b/services/spring-member/src/main/java/tum/devoops/memberservice/service/MemberService.java index e2c1fb4..d2fff0f 100644 --- a/services/spring-member/src/main/java/tum/devoops/memberservice/service/MemberService.java +++ b/services/spring-member/src/main/java/tum/devoops/memberservice/service/MemberService.java @@ -2,11 +2,14 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import tum.devoops.memberservice.converter.MemberConverter; +import tum.devoops.memberservice.entity.MemberEntity; import tum.devoops.memberservice.model.Member; import tum.devoops.memberservice.model.MemberCreate; import tum.devoops.memberservice.model.MemberSummary; +import tum.devoops.memberservice.repository.MemberRepository; -import java.time.LocalDate; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -17,46 +20,110 @@ public class MemberService { @Autowired KeycloakService keycloakService; + @Autowired + MemberRepository memberRepository; + public List getAllMembers() { - return List.of(); - } + List members = memberRepository.findAll(); + List memberSummaries = new ArrayList<>(); - public Optional getMemberById(UUID id) { - return Optional.empty(); + for (MemberEntity memberEntity : members) { + memberSummaries.add(MemberConverter.convertMemberEntityToMemberSummary(memberEntity)); + } + + return memberSummaries; } - public Optional getMemberDetailsById(UUID id) { - return Optional.empty(); + public Optional getMemberSummaryById(UUID id) { + Optional optionalMemberEntity = memberRepository.findById(id); + + if (optionalMemberEntity.isEmpty()) { + return Optional.empty(); + } + + MemberEntity memberEntity = optionalMemberEntity.get(); + MemberSummary memberSummary = MemberConverter.convertMemberEntityToMemberSummary(memberEntity); + + return Optional.of(memberSummary); } - private Member createMemberFromDTO(MemberCreate memberCreate, UUID id) { - return new Member( - id, - memberCreate.getFirstName(), - memberCreate.getLastName(), - memberCreate.getEmail(), - memberCreate.getBirthday(), - memberCreate.getPhoneNumber(), - memberCreate.getAddress(), - LocalDate.now(), - memberCreate.getInformation() - ); + public Optional getMemberById(UUID id) { + Optional optionalMemberEntity = memberRepository.findById(id); + + if (optionalMemberEntity.isEmpty()) { + return Optional.empty(); + } + + MemberEntity memberEntity = optionalMemberEntity.get(); + Member member = MemberConverter.convertMemberEntityToMember(memberEntity); + + return Optional.of(member); } - public Member createMember(MemberCreate memberCreate, String bearerToken) throws IllegalAccessException { - UUID id = keycloakService.createUser(memberCreate, bearerToken); - // TODO Store member in database - return createMemberFromDTO(memberCreate, id); + public Optional createMember(MemberCreate memberCreate, String bearerToken) { + // If a member with this email already exists + if (memberRepository.findByEmail(memberCreate.getEmail()).isPresent()) { + return Optional.empty(); + } + + UUID id; + try { + id = keycloakService.createUser(memberCreate, bearerToken); + } catch (Exception e) { + return Optional.empty(); + } + + MemberEntity memberEntity = MemberConverter.convertMemberCreateToMemberEntity(memberCreate, id); + memberEntity = memberRepository.save(memberEntity); + + Member member = MemberConverter.convertMemberEntityToMember(memberEntity); + + return Optional.of(member); } - public Optional updateMember(Member member) { - // TODO Update email in keycloak - return Optional.empty(); + public Optional updateMember(Member member, String bearerToken) { + + Optional memberEntityWithEmail = memberRepository.findByEmail(member.getEmail()); + + if (memberEntityWithEmail.isPresent()) { + // If a member other than the passed member has the email + if (!memberEntityWithEmail.get().getId().equals(member.getId())) { + return Optional.empty(); + } + } + + try { + keycloakService.updateUser(member, bearerToken); + } catch (Exception e) { + return Optional.empty(); + } + + MemberEntity memberEntity = MemberConverter.convertMemberToMemberEntity(member); + MemberEntity updatedMemberEntity = memberRepository.save(memberEntity); + Member updatedMember = MemberConverter.convertMemberEntityToMember(updatedMemberEntity); + + return Optional.of(updatedMember); } - public boolean deleteMember(UUID id) { - // TODO Additionally delete user in keycloak - return false; + public boolean deleteMember(UUID id, String bearerToken) { + + try { + keycloakService.deleteUser(id, bearerToken); + } + catch (Exception e) { + return false; + } + + Optional optionalMemberEntity = memberRepository.findById(id); + + if(optionalMemberEntity.isEmpty()) { + return false; + } + + MemberEntity memberEntity = optionalMemberEntity.get(); + memberRepository.delete(memberEntity); + + return true; } } diff --git a/services/spring-member/src/test/java/tum/devoops/memberservice/service/MemberServiceTest.java b/services/spring-member/src/test/java/tum/devoops/memberservice/service/MemberServiceTest.java new file mode 100644 index 0000000..2b74490 --- /dev/null +++ b/services/spring-member/src/test/java/tum/devoops/memberservice/service/MemberServiceTest.java @@ -0,0 +1,297 @@ +package tum.devoops.memberservice.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import tum.devoops.memberservice.entity.MemberEntity; +import tum.devoops.memberservice.model.Member; +import tum.devoops.memberservice.model.MemberCreate; +import tum.devoops.memberservice.model.MemberSummary; +import tum.devoops.memberservice.repository.MemberRepository; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class MemberServiceTest { + + private static final String TOKEN = "mock-token"; + + @Mock + private MemberRepository memberRepository; + + @Mock + private KeycloakService keycloakService; + + @InjectMocks + private MemberService memberService; + + private UUID id; + private MemberEntity memberEntity; + private Member member; + private MemberSummary expectedSummary; + private MemberCreate memberCreate; + + @BeforeEach + void setUp() { + id = UUID.randomUUID(); + + memberEntity = new MemberEntity( + id, + "firstName", + "lastName", + "email@email.com", + LocalDate.of(1990, 1, 1), + "phoneNumber", + "address", + LocalDate.of(2020, 6, 15), + "information" + ); + + member = new Member( + id, + "firstName", + "lastName", + "email@email.com", + LocalDate.of(1990, 1, 1), + "phoneNumber", + "address", + LocalDate.of(2020, 6, 15), + "information" + ); + + expectedSummary = new MemberSummary(id, "firstName", "lastName", "email@email.com"); + + memberCreate = new MemberCreate(); + memberCreate.setFirstName("firstName"); + memberCreate.setLastName("lastName"); + memberCreate.setEmail("email@email.com"); + memberCreate.setBirthday(LocalDate.of(1990, 1, 1)); + memberCreate.setPhoneNumber("phoneNumber"); + memberCreate.setAddress("address"); + memberCreate.setInformation("information"); + } + + // Test cases for getAllMembers() + + // Verifies that an empty list is returned when the repository holds no members + @Test + void getAllMembersReturnsEmptyListWhenNoMembers() { + when(memberRepository.findAll()).thenReturn(List.of()); + + List result = memberService.getAllMembers(); + + assertTrue(result.isEmpty()); + } + + // Verifies that each entity is converted into a MemberSummary with its fields mapped + @Test + void getAllMembersReturnsSummaryPerEntity() { + when(memberRepository.findAll()).thenReturn(List.of(memberEntity)); + + List result = memberService.getAllMembers(); + + assertEquals(1, result.size()); + assertEquals(expectedSummary, result.getFirst()); + } + + // Test cases for getMemberSummaryById() + + // Verifies that a populated summary is returned when the member exists + @Test + void getMemberSummaryByIdReturnsSummaryWhenFound() { + when(memberRepository.findById(id)).thenReturn(Optional.of(memberEntity)); + + Optional result = memberService.getMemberSummaryById(id); + + assertTrue(result.isPresent()); + assertEquals(expectedSummary, result.get()); + } + + // Verifies that an empty optional is returned when the member does not exist + @Test + void getMemberSummaryByIdReturnsEmptyWhenNotFound() { + when(memberRepository.findById(id)).thenReturn(Optional.empty()); + + Optional result = memberService.getMemberSummaryById(id); + + assertTrue(result.isEmpty()); + } + + // Test cases for getMemberById() + + // Verifies that the full member is returned when it exists + @Test + void getMemberByIdReturnsMemberWhenFound() { + when(memberRepository.findById(id)).thenReturn(Optional.of(memberEntity)); + + Optional result = memberService.getMemberById(id); + + assertTrue(result.isPresent()); + assertEquals(member, result.get()); + } + + // Verifies that an empty optional is returned when the member does not exist + @Test + void getMemberByIdReturnsEmptyWhenNotFound() { + when(memberRepository.findById(id)).thenReturn(Optional.empty()); + + Optional result = memberService.getMemberById(id); + + assertTrue(result.isEmpty()); + } + + // Test cases for createMember() + + // Verifies that creation is rejected when a member with the same email already exists + @Test + void createMemberReturnsEmptyWhenEmailExists() { + when(memberRepository.findByEmail("email@email.com")).thenReturn(Optional.of(memberEntity)); + + Optional result = memberService.createMember(memberCreate, TOKEN); + + assertTrue(result.isEmpty()); + verifyNoInteractions(keycloakService); + verify(memberRepository, never()).save(any()); + } + + // Verifies that creation is rejected and nothing is persisted when Keycloak fails + @Test + void createMemberReturnsEmptyWhenKeycloakThrows() throws Exception { + when(memberRepository.findByEmail("email@email.com")).thenReturn(Optional.empty()); + when(keycloakService.createUser(memberCreate, TOKEN)).thenThrow(new RuntimeException("keycloak down")); + + Optional result = memberService.createMember(memberCreate, TOKEN); + + assertTrue(result.isEmpty()); + verify(memberRepository, never()).save(any()); + } + + // Verifies that a member is created and returned when the email is free and Keycloak succeeds + @Test + void createMemberReturnsMemberOnSuccess() throws Exception { + when(memberRepository.findByEmail("email@email.com")).thenReturn(Optional.empty()); + when(keycloakService.createUser(memberCreate, TOKEN)).thenReturn(id); + when(memberRepository.save(any(MemberEntity.class))).thenReturn(memberEntity); + + Optional result = memberService.createMember(memberCreate, TOKEN); + + assertTrue(result.isPresent()); + assertEquals(member, result.get()); + verify(memberRepository).save(any(MemberEntity.class)); + } + + // Test cases for updateMember() + + // Verifies that an update is rejected when the email belongs to a different member + @Test + void updateMemberReturnsEmptyWhenEmailTakenByOther() { + MemberEntity otherMember = new MemberEntity( + UUID.randomUUID(), + "other", + "other", + "email@email.com", + LocalDate.of(1990, 1, 1), + "phoneNumber", + "address", + LocalDate.of(2020, 6, 15), + "information" + ); + when(memberRepository.findByEmail("email@email.com")).thenReturn(Optional.of(otherMember)); + + Optional result = memberService.updateMember(member, TOKEN); + + assertTrue(result.isEmpty()); + verifyNoInteractions(keycloakService); + verify(memberRepository, never()).save(any()); + } + + // Verifies that an update succeeds when the email belongs to the same member + @Test + void updateMemberReturnsMemberWhenEmailBelongsToSameMember() { + when(memberRepository.findByEmail("email@email.com")).thenReturn(Optional.of(memberEntity)); + when(memberRepository.save(any(MemberEntity.class))).thenReturn(memberEntity); + + Optional result = memberService.updateMember(member, TOKEN); + + assertTrue(result.isPresent()); + assertEquals(member, result.get()); + verify(keycloakService).updateUser(member, TOKEN); + verify(memberRepository).save(any(MemberEntity.class)); + } + + // Verifies that an update succeeds when the email is not used by anyone + @Test + void updateMemberReturnsMemberWhenEmailUnused() { + when(memberRepository.findByEmail("email@email.com")).thenReturn(Optional.empty()); + when(memberRepository.save(any(MemberEntity.class))).thenReturn(memberEntity); + + Optional result = memberService.updateMember(member, TOKEN); + + assertTrue(result.isPresent()); + assertEquals(member, result.get()); + verify(memberRepository).save(any(MemberEntity.class)); + } + + // Verifies that an update is rejected and nothing is persisted when Keycloak fails + @Test + void updateMemberReturnsEmptyWhenKeycloakThrows() { + when(memberRepository.findByEmail("email@email.com")).thenReturn(Optional.empty()); + doThrow(new RuntimeException("keycloak down")).when(keycloakService).updateUser(member, TOKEN); + + Optional result = memberService.updateMember(member, TOKEN); + + assertTrue(result.isEmpty()); + verify(memberRepository, never()).save(any()); + } + + // Test cases for deleteMember() + + // Verifies that deletion fails and nothing is removed when Keycloak fails + @Test + void deleteMemberReturnsFalseWhenKeycloakThrows() { + doThrow(new RuntimeException("keycloak down")).when(keycloakService).deleteUser(id, TOKEN); + + boolean result = memberService.deleteMember(id, TOKEN); + + assertFalse(result); + verify(memberRepository, never()).delete(any()); + } + + // Verifies that deletion fails when the member does not exist in the repository + @Test + void deleteMemberReturnsFalseWhenMemberNotFound() { + when(memberRepository.findById(id)).thenReturn(Optional.empty()); + + boolean result = memberService.deleteMember(id, TOKEN); + + assertFalse(result); + verify(memberRepository, never()).delete(any()); + } + + // Verifies that deletion succeeds and the entity is removed when it exists + @Test + void deleteMemberReturnsTrueOnSuccess() { + when(memberRepository.findById(id)).thenReturn(Optional.of(memberEntity)); + + boolean result = memberService.deleteMember(id, TOKEN); + + assertTrue(result); + verify(memberRepository).delete(memberEntity); + } +} From 0336bcc876747bce19c668f9e634be14d1dd0212 Mon Sep 17 00:00:00 2001 From: f-s-h Date: Fri, 19 Jun 2026 14:17:17 +0200 Subject: [PATCH 27/28] Fixed linting errors --- .../controller/MemberController.java | 25 +++++++++++-------- .../memberservice/entity/MemberEntity.java | 2 +- .../service/KeycloakService.java | 3 ++- .../memberservice/service/MemberService.java | 5 ++-- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java b/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java index b78ad0f..f8a244c 100644 --- a/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java +++ b/services/spring-member/src/main/java/tum/devoops/memberservice/controller/MemberController.java @@ -5,14 +5,18 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; import tum.devoops.memberservice.model.Member; import tum.devoops.memberservice.model.MemberCreate; import tum.devoops.memberservice.model.MemberSummary; import tum.devoops.memberservice.service.MemberService; -import javax.swing.text.html.Option; import java.net.URI; import java.util.List; import java.util.Optional; @@ -44,7 +48,8 @@ public ResponseEntity> getAllMembers() { * This endpoint searches the primary database and returns the corresponding member to an ID. *

* @param id The unique {@link UUID} of the member. - * @return ResponseEntity containing a MemberSummary and HTTP 200. If the member is not found an empty ResponseEntity with HTTP 404 is returned. + * @return ResponseEntity containing a MemberSummary and HTTP 200. If the member is not found an empty + * ResponseEntity with HTTP 404 is returned. */ @PreAuthorize("hasAnyRole('member', 'admin')") @GetMapping("/{id}") @@ -54,8 +59,7 @@ public ResponseEntity getMemberSummaryById(@PathVariable UUID id) if (memberOptional.isPresent()) { MemberSummary member = memberOptional.get(); return ResponseEntity.ok(member); - } - else { + } else { return ResponseEntity.notFound().build(); } } @@ -66,7 +70,8 @@ public ResponseEntity getMemberSummaryById(@PathVariable UUID id) * This endpoint searches the primary database and returns the corresponding member to an ID. *

* @param id The unique {@link UUID} of the member. - * @return ResponseEntity containing a Member and HTTP 200. If the member is not found an empty ResponseEntity with HTTP 404 is returned. + * @return ResponseEntity containing a Member and HTTP 200. If the member is not found an empty + * ResponseEntity with HTTP 404 is returned. */ @PreAuthorize("hasAnyRole('member', 'admin')") @GetMapping("/{id}/details") @@ -76,8 +81,7 @@ public ResponseEntity getMemberById(@PathVariable UUID id) { if (memberOptional.isPresent()) { Member member = memberOptional.get(); return ResponseEntity.ok(member); - } - else { + } else { return ResponseEntity.notFound().build(); } } @@ -143,8 +147,7 @@ public ResponseEntity deleteMember(@PathVariable UUID id, @Authenticatio if (isDeleted) { return ResponseEntity.noContent().build(); - } - else { + } else { return ResponseEntity.notFound().build(); } } diff --git a/services/spring-member/src/main/java/tum/devoops/memberservice/entity/MemberEntity.java b/services/spring-member/src/main/java/tum/devoops/memberservice/entity/MemberEntity.java index 1b47743..9b1586f 100644 --- a/services/spring-member/src/main/java/tum/devoops/memberservice/entity/MemberEntity.java +++ b/services/spring-member/src/main/java/tum/devoops/memberservice/entity/MemberEntity.java @@ -50,7 +50,7 @@ public class MemberEntity { @Override public boolean equals(Object o) { - if(!(o instanceof MemberEntity)) { + if (!(o instanceof MemberEntity)) { return false; } MemberEntity other = (MemberEntity) o; diff --git a/services/spring-member/src/main/java/tum/devoops/memberservice/service/KeycloakService.java b/services/spring-member/src/main/java/tum/devoops/memberservice/service/KeycloakService.java index c07b01d..6205891 100644 --- a/services/spring-member/src/main/java/tum/devoops/memberservice/service/KeycloakService.java +++ b/services/spring-member/src/main/java/tum/devoops/memberservice/service/KeycloakService.java @@ -56,7 +56,8 @@ public UUID createUser(MemberCreate member, String bearerToken) throws Exception public void updateUser(Member member, String bearerToken) throws HttpClientErrorException{ - UserRepresentation body = new UserRepresentation(member.getEmail(), member.getFirstName(), member.getLastName(), member.getEmail() ,true); + UserRepresentation body = new UserRepresentation(member.getEmail(), member.getFirstName(), + member.getLastName(), member.getEmail(), true); try { restClient.put() diff --git a/services/spring-member/src/main/java/tum/devoops/memberservice/service/MemberService.java b/services/spring-member/src/main/java/tum/devoops/memberservice/service/MemberService.java index d2fff0f..c2cdeb7 100644 --- a/services/spring-member/src/main/java/tum/devoops/memberservice/service/MemberService.java +++ b/services/spring-member/src/main/java/tum/devoops/memberservice/service/MemberService.java @@ -109,14 +109,13 @@ public boolean deleteMember(UUID id, String bearerToken) { try { keycloakService.deleteUser(id, bearerToken); - } - catch (Exception e) { + } catch (Exception e) { return false; } Optional optionalMemberEntity = memberRepository.findById(id); - if(optionalMemberEntity.isEmpty()) { + if (optionalMemberEntity.isEmpty()) { return false; } From ffc8c782a126fd111e9eb9c4d553640954300a92 Mon Sep 17 00:00:00 2001 From: f-s-h Date: Fri, 19 Jun 2026 14:45:08 +0200 Subject: [PATCH 28/28] Fixed linting errors --- .../controller/MemberControllerTest.java | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/services/spring-member/src/test/java/tum/devoops/memberservice/controller/MemberControllerTest.java b/services/spring-member/src/test/java/tum/devoops/memberservice/controller/MemberControllerTest.java index 3aaf727..3868e16 100644 --- a/services/spring-member/src/test/java/tum/devoops/memberservice/controller/MemberControllerTest.java +++ b/services/spring-member/src/test/java/tum/devoops/memberservice/controller/MemberControllerTest.java @@ -23,11 +23,16 @@ import java.util.Optional; import java.util.UUID; -import static org.mockito.ArgumentMatchers.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @WebMvcTest(MemberController.class) @Import(SecurityConfig.class) @@ -422,7 +427,8 @@ void updateMemberAllowedForUserSameId() throws Exception { .andExpect(status().isOk()); } - // Verifies that a user with an undefined role other than "admin" and "member" is not allowed to update a member that is not himself (401 forbidden) + // Verifies that a user with an undefined role other than "admin" and "member" is not allowed + // to update a member that is not himself (401 forbidden) @Test void updateMemberNotAllowedForUndefinedRoleOtherId() throws Exception { UUID randomId = UUID.randomUUID(); @@ -437,7 +443,8 @@ void updateMemberNotAllowedForUndefinedRoleOtherId() throws Exception { .andExpect(status().isForbidden()); } - // Verifies that a user with an undefined role other than "admin" and "member" is not allowed to update a member that is himself (401 forbidden) + // Verifies that a user with an undefined role other than "admin" and "member" is not allowed + // to update a member that is himself (401 forbidden) @Test void updateMemberNotAllowedForUndefinedRoleSameId() throws Exception { mockMvc.perform(put(String.format("/%s", member.getId())) @@ -570,7 +577,8 @@ void deleteMemberNotAllowedForUserSameId() throws Exception { .andExpect(status().isForbidden()); } - // Verifies that a user with an undefined role other than "admin" and "member" is not allowed to delete a member that is not himself (401 forbidden) + // Verifies that a user with an undefined role other than "admin" and "member" is not allowed + // to delete a member that is not himself (401 forbidden) @Test void deleteMemberNotAllowedForUndefinedRoleOtherId() throws Exception { UUID randomId = UUID.randomUUID(); @@ -583,7 +591,8 @@ void deleteMemberNotAllowedForUndefinedRoleOtherId() throws Exception { .andExpect(status().isForbidden()); } - // Verifies that a user with an undefined role other than "admin" and "member" is not allowed to delete a member that is himself (401 forbidden) + // Verifies that a user with an undefined role other than "admin" and "member" is not allowed + // to delete a member that is himself (401 forbidden) @Test void deleteMemberNotAllowedForUndefinedRoleSameId() throws Exception { mockMvc.perform(delete(String.format("/%s", member.getId()))