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()))