diff --git a/pom.xml b/pom.xml
index 4d23ebe..c269b1f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -209,6 +209,10 @@
loki-logback-appender
1.5.2
-->
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jdk8
+
diff --git a/src/main/java/org/patinanetwork/patchats/api/member/MemberController.java b/src/main/java/org/patinanetwork/patchats/api/member/MemberController.java
index 1d615d6..7c0ccc7 100644
--- a/src/main/java/org/patinanetwork/patchats/api/member/MemberController.java
+++ b/src/main/java/org/patinanetwork/patchats/api/member/MemberController.java
@@ -2,11 +2,15 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
+import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.patinanetwork.patchats.api.member.dto.CreateMemberRequest;
import org.patinanetwork.patchats.api.member.dto.MemberDto;
+import org.patinanetwork.patchats.api.member.dto.UpdateMemberRequest;
import org.patinanetwork.patchats.common.dto.ApiResponder;
import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PatchMapping;
+import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -26,13 +30,12 @@ public ResponseEntity> createMember(@Valid @RequestBody
return ResponseEntity.ok(ApiResponder.success("Member created successfully", response));
}
- // TODO: Implement these endpoints after createMember is fully functional and tested
- // @PatchMapping("/{id}")
- // public ResponseEntity> updateMember(
- // @Valid @RequestBody final UpdateMemberRequest request, @PathVariable final UUID id) {
- // final MemberDto response = memberService.updateMember(request, id);
- // return ResponseEntity.ok(ApiResponder.success("Member updated successfully", response));
- // }
+ @PatchMapping("/{id}")
+ public ResponseEntity> updateMember(
+ @Valid @RequestBody final UpdateMemberRequest request, @PathVariable final UUID id) {
+ final MemberDto response = memberService.updateMember(request, id);
+ return ResponseEntity.ok(ApiResponder.success("Member updated successfully", response));
+ }
// @GetMapping("/{id}")
// public ResponseEntity> getMember(@PathVariable final UUID id) {
diff --git a/src/main/java/org/patinanetwork/patchats/api/member/MemberService.java b/src/main/java/org/patinanetwork/patchats/api/member/MemberService.java
index 169b9ad..29c7c2e 100644
--- a/src/main/java/org/patinanetwork/patchats/api/member/MemberService.java
+++ b/src/main/java/org/patinanetwork/patchats/api/member/MemberService.java
@@ -1,7 +1,9 @@
package org.patinanetwork.patchats.api.member;
import java.util.List;
+import java.util.Optional;
import java.util.UUID;
+import java.util.function.Consumer;
import lombok.RequiredArgsConstructor;
import org.patinanetwork.patchats.api.member.db.models.Member;
import org.patinanetwork.patchats.api.member.db.repos.MemberRepo;
@@ -10,6 +12,7 @@
import org.patinanetwork.patchats.api.member.dto.UpdateMemberRequest;
import org.patinanetwork.patchats.common.web.exception.MemberDuplicateException;
import org.patinanetwork.patchats.common.web.exception.MemberNotFoundException;
+import org.patinanetwork.patchats.common.web.exception.ValidationException;
import org.springframework.stereotype.Service;
@Service
@@ -48,40 +51,53 @@ public List getMembers() {
public MemberDto updateMember(UpdateMemberRequest request, UUID id) {
Member member = memberRepo.getMemberById(id).orElseThrow(() -> new MemberNotFoundException(id));
- if (request.firstName() != null) {
- member.setFirstName(request.firstName());
- }
- if (request.lastName() != null) {
- member.setLastName(request.lastName());
- }
- if (request.email() != null) {
- member.setEmail(request.email());
- }
- if (request.linkedInUrl() != null) {
- member.setLinkedInUrl(request.linkedInUrl());
- }
- if (request.introduction() != null) {
- member.setIntroduction(request.introduction());
- }
- if (request.matchPref() != null) {
- member.setMatchPref(request.matchPref());
- }
- if (request.industryPref() != null) {
- member.setIndustryPref(request.industryPref());
- }
- if (request.rolePref() != null) {
- member.setRolePref(request.rolePref());
- }
- if (request.topics() != null) {
- member.setTopics(request.topics());
- }
- if (request.extraNotes() != null) {
- member.setExtraNotes(request.extraNotes());
- }
+ validateAndUpdateRequired(member, request);
+ updateOptional(member, request);
+
Member updatedMember = memberRepo.updateMember(member).orElseThrow(() -> new MemberNotFoundException(id));
return MemberDto.from(updatedMember);
}
+ private void validateAndUpdateRequired(Member member, UpdateMemberRequest request) {
+ validateAndUpdate(request.firstName(), member::setFirstName, "firstName");
+ validateAndUpdate(request.lastName(), member::setLastName, "lastName");
+ validateAndUpdateEmail(member, request);
+ validateAndUpdate(request.introduction(), member::setIntroduction, "introduction");
+ }
+
+ private void validateAndUpdate(Optional field, Consumer setter, String fieldName) {
+ if (field.isPresent()) {
+ String value = field.get();
+ if (value.isBlank()) {
+ throw new ValidationException(fieldName + " cannot be empty");
+ }
+ setter.accept(value);
+ }
+ }
+
+ private void validateAndUpdateEmail(Member member, UpdateMemberRequest request) {
+ if (request.email().isPresent()) {
+ String email = request.email().get();
+ if (email.isBlank()) {
+ throw new ValidationException("email cannot be empty");
+ }
+ if (!email.equals(member.getEmail())
+ && memberRepo.getMemberByEmail(email).isPresent()) {
+ throw new MemberDuplicateException(email);
+ }
+ member.setEmail(email);
+ }
+ }
+
+ private void updateOptional(Member member, UpdateMemberRequest request) {
+ request.linkedInUrl().ifPresent(member::setLinkedInUrl);
+ request.matchPref().ifPresent(member::setMatchPref);
+ request.industryPref().ifPresent(member::setIndustryPref);
+ request.rolePref().ifPresent(member::setRolePref);
+ request.topics().ifPresent(member::setTopics);
+ request.extraNotes().ifPresent(member::setExtraNotes);
+ }
+
// TODO: Implement these methods after createMember and updateMember is fully functional and tested
// public MemberDto getMemberById(UUID id) {
// return memberRepo
diff --git a/src/main/java/org/patinanetwork/patchats/api/member/db/repos/MemberSqlRepo.java b/src/main/java/org/patinanetwork/patchats/api/member/db/repos/MemberSqlRepo.java
index ccc65d4..cabf9a8 100644
--- a/src/main/java/org/patinanetwork/patchats/api/member/db/repos/MemberSqlRepo.java
+++ b/src/main/java/org/patinanetwork/patchats/api/member/db/repos/MemberSqlRepo.java
@@ -37,6 +37,22 @@ private Member parseResultSetToMember(final ResultSet rs) throws SQLException {
.build();
}
+ private JdbcClient.StatementSpec bindMemberParams(JdbcClient.StatementSpec spec, Member member) {
+ return spec.param("id", member.getId())
+ .param("first_name", member.getFirstName())
+ .param("last_name", member.getLastName())
+ .param("email", member.getEmail())
+ .param("linked_in_url", member.getLinkedInUrl())
+ .param("introduction", member.getIntroduction())
+ .param("referral_source", member.getReferralSource())
+ .param("active", member.isActive())
+ .param("match_pref", member.getMatchPref())
+ .param("industry_pref", member.getIndustryPref())
+ .param("role_pref", member.getRolePref())
+ .param("topics", member.getTopics())
+ .param("extra_notes", member.getExtraNotes());
+ }
+
@Override
public Member createMember(Member member) {
String sql = """
@@ -73,20 +89,7 @@ public Member createMember(Member member) {
RETURNING
*
""";
- return jdbc.sql(sql)
- .param("id", member.getId())
- .param("first_name", member.getFirstName())
- .param("last_name", member.getLastName())
- .param("email", member.getEmail())
- .param("linked_in_url", member.getLinkedInUrl())
- .param("introduction", member.getIntroduction())
- .param("referral_source", member.getReferralSource())
- .param("active", member.isActive())
- .param("match_pref", member.getMatchPref())
- .param("industry_pref", member.getIndustryPref())
- .param("role_pref", member.getRolePref())
- .param("topics", member.getTopics())
- .param("extra_notes", member.getExtraNotes())
+ return bindMemberParams(jdbc.sql(sql), member)
.query((rs, rowNum) -> parseResultSetToMember(rs))
.single();
}
@@ -99,12 +102,36 @@ public List getMembers() {
@Override
public Optional updateMember(Member member) {
- throw new UnsupportedOperationException("Not implemented yet");
+ String sql = """
+ UPDATE "members" SET
+ "first_name" = :first_name,
+ "last_name" = :last_name,
+ "email" = :email,
+ "linked_in_url" = :linked_in_url,
+ "introduction" = :introduction,
+ "referral_source" = :referral_source,
+ "active" = :active,
+ "match_pref" = :match_pref,
+ "industry_pref" = :industry_pref,
+ "role_pref" = :role_pref,
+ "topics" = :topics,
+ "extra_notes" = :extra_notes,
+ "updated_at" = NOW()
+ WHERE "id" = :id
+ RETURNING *
+ """;
+ return bindMemberParams(jdbc.sql(sql), member)
+ .query((rs, rowNum) -> parseResultSetToMember(rs))
+ .optional();
}
@Override
public Optional getMemberById(UUID id) {
- throw new UnsupportedOperationException("Not implemented yet");
+ String sql = "SELECT * FROM members WHERE id = :id";
+ return jdbc.sql(sql)
+ .param("id", id)
+ .query((rs, rowNum) -> parseResultSetToMember(rs))
+ .optional();
}
@Override
diff --git a/src/main/java/org/patinanetwork/patchats/api/member/dto/UpdateMemberRequest.java b/src/main/java/org/patinanetwork/patchats/api/member/dto/UpdateMemberRequest.java
index 290ec98..83d1eaa 100644
--- a/src/main/java/org/patinanetwork/patchats/api/member/dto/UpdateMemberRequest.java
+++ b/src/main/java/org/patinanetwork/patchats/api/member/dto/UpdateMemberRequest.java
@@ -1,16 +1,16 @@
package org.patinanetwork.patchats.api.member.dto;
import jakarta.validation.constraints.Email;
+import java.util.Optional;
public record UpdateMemberRequest(
- String firstName,
- String lastName,
- // TODO: Changes to email require verification after authentication is implemented
- @Email String email,
- String linkedInUrl,
- String introduction,
- String matchPref,
- String industryPref,
- String rolePref,
- String topics,
- String extraNotes) {}
+ Optional firstName,
+ Optional lastName,
+ Optional<@Email String> email,
+ Optional linkedInUrl,
+ Optional introduction,
+ Optional matchPref,
+ Optional industryPref,
+ Optional rolePref,
+ Optional topics,
+ Optional extraNotes) {}
diff --git a/src/main/java/org/patinanetwork/patchats/common/web/ApiExceptionHandler.java b/src/main/java/org/patinanetwork/patchats/common/web/ApiExceptionHandler.java
index 7501ad8..e8b60c7 100644
--- a/src/main/java/org/patinanetwork/patchats/common/web/ApiExceptionHandler.java
+++ b/src/main/java/org/patinanetwork/patchats/common/web/ApiExceptionHandler.java
@@ -4,6 +4,7 @@
import org.patinanetwork.patchats.common.dto.ApiResponder;
import org.patinanetwork.patchats.common.web.exception.MemberDuplicateException;
import org.patinanetwork.patchats.common.web.exception.MemberNotFoundException;
+import org.patinanetwork.patchats.common.web.exception.ValidationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
@@ -34,6 +35,11 @@ public ResponseEntity> handleMemberDuplicate(final MemberDupl
return ResponseEntity.status(HttpStatus.CONFLICT).body(ApiResponder.failure(ex.getMessage()));
}
+ @ExceptionHandler(ValidationException.class)
+ public ResponseEntity> handleValidation(ValidationException ex) {
+ return ResponseEntity.badRequest().body(ApiResponder.failure(ex.getMessage()));
+ }
+
private String formatError(final FieldError error) {
return error.getField() + " " + error.getDefaultMessage();
}
diff --git a/src/main/java/org/patinanetwork/patchats/common/web/exception/ValidationException.java b/src/main/java/org/patinanetwork/patchats/common/web/exception/ValidationException.java
new file mode 100644
index 0000000..4e97d6a
--- /dev/null
+++ b/src/main/java/org/patinanetwork/patchats/common/web/exception/ValidationException.java
@@ -0,0 +1,11 @@
+package org.patinanetwork.patchats.common.web.exception;
+
+public class ValidationException extends RuntimeException {
+ public ValidationException(String message) {
+ super(message);
+ }
+
+ public ValidationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/test/java/org/patinanetwork/patchats/api/member/MemberControllerTest.java b/src/test/java/org/patinanetwork/patchats/api/member/MemberControllerTest.java
index 8532e87..05d20a3 100644
--- a/src/test/java/org/patinanetwork/patchats/api/member/MemberControllerTest.java
+++ b/src/test/java/org/patinanetwork/patchats/api/member/MemberControllerTest.java
@@ -1,17 +1,26 @@
package org.patinanetwork.patchats.api.member;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.time.OffsetDateTime;
+import java.util.Optional;
import java.util.UUID;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.patinanetwork.patchats.api.member.dto.CreateMemberRequest;
import org.patinanetwork.patchats.api.member.dto.MemberDto;
+import org.patinanetwork.patchats.api.member.dto.UpdateMemberRequest;
import org.patinanetwork.patchats.common.web.ApiExceptionHandler;
import org.patinanetwork.patchats.common.web.exception.MemberDuplicateException;
+import org.patinanetwork.patchats.common.web.exception.MemberNotFoundException;
+import org.patinanetwork.patchats.common.web.exception.ValidationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
@@ -31,8 +40,15 @@ class MemberControllerTest {
@MockitoBean
private MemberService memberService;
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ @BeforeEach
+ void setUp() {
+ objectMapper.registerModule(new com.fasterxml.jackson.datatype.jdk8.Jdk8Module());
+ }
+
@Test
- void createMemberReturnsOkAndMemberDto() throws Exception {
+ void createMember_returnsOkAndMemberDto() throws Exception {
final CreateMemberRequest request = new CreateMemberRequest(
"John",
"Doe",
@@ -106,4 +122,214 @@ void createMemberReturnsConflictOnDuplicateEmail() throws Exception {
.andExpect(status().isConflict())
.andExpect(jsonPath("$.success").value(false));
}
+
+ @Test
+ void updateMember_successWithValidRequest() throws Exception {
+ final UUID id = UUID.randomUUID();
+ final UpdateMemberRequest request = new UpdateMemberRequest(
+ Optional.of("UpdatedFirstName"),
+ Optional.of("UpdatedLastName"),
+ Optional.of("updated@example.com"),
+ Optional.of("https://linkedin.com/in/updated"),
+ Optional.of("Updated intro"),
+ Optional.of("Mentor"),
+ Optional.of("Tech"),
+ Optional.of("Engineer"),
+ Optional.of("AI,ML"),
+ Optional.of("Notes"));
+
+ final MemberDto response = MemberDto.builder()
+ .id(id)
+ .firstName("UpdatedFirstName")
+ .lastName("UpdatedLastName")
+ .email("updated@example.com")
+ .linkedInUrl("https://linkedin.com/in/updated")
+ .introduction("Updated intro")
+ .referralSource("Referral")
+ .active(true)
+ .matchPref("Mentor")
+ .industryPref("Tech")
+ .rolePref("Engineer")
+ .topics("AI,ML")
+ .extraNotes("Notes")
+ .createdAt(OffsetDateTime.now())
+ .updatedAt(OffsetDateTime.now())
+ .build();
+
+ when(memberService.updateMember(any(UpdateMemberRequest.class), eq(id))).thenReturn(response);
+
+ mockMvc.perform(patch("/api/members/{id}", id)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(request)))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.success").value(true))
+ .andExpect(jsonPath("$.message").value("Member updated successfully"))
+ .andExpect(jsonPath("$.payload.id").value(id.toString()))
+ .andExpect(jsonPath("$.payload.firstName").value("UpdatedFirstName"))
+ .andExpect(jsonPath("$.payload.lastName").value("UpdatedLastName"))
+ .andExpect(jsonPath("$.payload.email").value("updated@example.com"));
+ }
+
+ @Test
+ void updateMember_successWithPartialUpdate() throws Exception {
+ final UUID id = UUID.randomUUID();
+ final UpdateMemberRequest request = new UpdateMemberRequest(
+ Optional.of("UpdatedFirstName"),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty());
+
+ final MemberDto response = MemberDto.builder()
+ .id(id)
+ .firstName("UpdatedFirstName")
+ .lastName("OldLastName")
+ .email("old@example.com")
+ .linkedInUrl("https://linkedin.com/in/old")
+ .introduction("Old intro")
+ .referralSource("Referral")
+ .active(true)
+ .matchPref("Mentor")
+ .industryPref("Finance")
+ .rolePref("Analyst")
+ .topics("Economics")
+ .extraNotes("Old notes")
+ .createdAt(OffsetDateTime.now())
+ .updatedAt(OffsetDateTime.now())
+ .build();
+
+ when(memberService.updateMember(any(UpdateMemberRequest.class), eq(id))).thenReturn(response);
+
+ mockMvc.perform(patch("/api/members/{id}", id)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(request)))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.success").value(true))
+ .andExpect(jsonPath("$.payload.firstName").value("UpdatedFirstName"));
+ }
+
+ @Test
+ void updateMember_badRequestWhenBlankRequiredField() throws Exception {
+ final UUID id = UUID.randomUUID();
+ final UpdateMemberRequest request = new UpdateMemberRequest(
+ Optional.of(""),
+ Optional.of("UpdatedLastName"),
+ Optional.of("updated@example.com"),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty());
+
+ when(memberService.updateMember(any(), any())).thenThrow(new ValidationException("firstName cannot be empty"));
+
+ mockMvc.perform(patch("/api/members/{id}", id)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(request)))
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.message").value("firstName cannot be empty"));
+ }
+
+ @Test
+ void updateMember_notFoundWhenMemberDoesNotExist() throws Exception {
+ final UUID id = UUID.randomUUID();
+ final UpdateMemberRequest request = new UpdateMemberRequest(
+ Optional.of("UpdatedFirstName"),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty());
+
+ when(memberService.updateMember(any(UpdateMemberRequest.class), eq(id)))
+ .thenThrow(new MemberNotFoundException(id));
+
+ mockMvc.perform(patch("/api/members/{id}", id)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(request)))
+ .andExpect(status().isNotFound())
+ .andExpect(jsonPath("$.success").value(false));
+ }
+
+ @Test
+ void updateMember_conflictWhenEmailIsDuplicate() throws Exception {
+ final UUID id = UUID.randomUUID();
+ final UpdateMemberRequest request = new UpdateMemberRequest(
+ Optional.empty(),
+ Optional.empty(),
+ Optional.of("existing@example.com"),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty());
+
+ when(memberService.updateMember(any(UpdateMemberRequest.class), eq(id)))
+ .thenThrow(new MemberDuplicateException("existing@example.com"));
+
+ mockMvc.perform(patch("/api/members/{id}", id)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(request)))
+ .andExpect(status().isConflict())
+ .andExpect(jsonPath("$.success").value(false));
+ }
+
+ @Test
+ void updateMember_badRequestWhenValidationFails() throws Exception {
+ final UUID id = UUID.randomUUID();
+ final UpdateMemberRequest request = new UpdateMemberRequest(
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty());
+
+ when(memberService.updateMember(any(UpdateMemberRequest.class), eq(id)))
+ .thenThrow(new ValidationException("firstName cannot be empty"));
+
+ mockMvc.perform(patch("/api/members/{id}", id)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(request)))
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.success").value(false))
+ .andExpect(jsonPath("$.message").value("firstName cannot be empty"));
+ }
+
+ @Test
+ void updateMember_badRequestWhenInvalidUuid() throws Exception {
+ final UpdateMemberRequest request = new UpdateMemberRequest(
+ Optional.of("UpdatedFirstName"),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty());
+
+ mockMvc.perform(patch("/api/members/{id}", "invalid-uuid")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(request)))
+ .andExpect(status().isBadRequest());
+ }
}
diff --git a/src/test/java/org/patinanetwork/patchats/api/member/MemberServiceTest.java b/src/test/java/org/patinanetwork/patchats/api/member/MemberServiceTest.java
index 8bf3318..e8f35d3 100644
--- a/src/test/java/org/patinanetwork/patchats/api/member/MemberServiceTest.java
+++ b/src/test/java/org/patinanetwork/patchats/api/member/MemberServiceTest.java
@@ -116,16 +116,16 @@ void createMemberThrowsExceptionWhenEmailAlreadyExists() {
void updateMember_throwsExceptionWhenMemberNotFound() {
final UUID id = UUID.randomUUID();
final UpdateMemberRequest request = new UpdateMemberRequest(
- "UpdatedFirstName",
- "UpdatedLastName",
- "updated@example.com",
- "https://linkedin.com/in/updated",
- "Updated intro",
- "Mentor - I am looking for guidance from someone with more experience",
- "Technology",
- "Software Engineer",
- "AI,ML",
- "Notes");
+ Optional.of("UpdatedFirstName"),
+ Optional.of("UpdatedLastName"),
+ Optional.of("updated@example.com"),
+ Optional.of("https://linkedin.com/in/updated"),
+ Optional.of("Updated intro"),
+ Optional.of("Mentor - I am looking for guidance from someone with more experience"),
+ Optional.of("Technology"),
+ Optional.of("Software Engineer"),
+ Optional.of("AI,ML"),
+ Optional.of("Notes"));
when(memberRepo.getMemberById(id)).thenReturn(Optional.empty());
@@ -136,8 +136,17 @@ void updateMember_throwsExceptionWhenMemberNotFound() {
@Test
void updateMember_successWithOnlyNameField() {
final UUID id = UUID.randomUUID();
- final UpdateMemberRequest request =
- new UpdateMemberRequest("UpdatedFirstName", null, null, null, null, null, null, null, null, null);
+ final UpdateMemberRequest request = new UpdateMemberRequest(
+ Optional.of("UpdatedFirstName"),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty());
final Member existingMember = Member.builder()
.id(id)
@@ -181,8 +190,17 @@ void updateMember_successWithOnlyNameField() {
@Test
void updateMember_successWithAllNullFields() {
final UUID id = UUID.randomUUID();
- final UpdateMemberRequest request =
- new UpdateMemberRequest(null, null, null, null, null, null, null, null, null, null);
+ final UpdateMemberRequest request = new UpdateMemberRequest(
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty());
final Member existingMember = Member.builder()
.id(id)
@@ -214,16 +232,16 @@ void updateMember_successWithAllNullFields() {
void updateMember_successWithAllFields() {
final UUID id = UUID.randomUUID();
final UpdateMemberRequest request = new UpdateMemberRequest(
- "UpdatedFirstName",
- "UpdatedLastName",
- "updated@example.com",
- "https://linkedin.com/in/updated",
- "Updated intro",
- "Mentor",
- "Tech",
- "Engineer",
- "AI,ML",
- "Notes");
+ Optional.of("UpdatedFirstName"),
+ Optional.of("UpdatedLastName"),
+ Optional.of("updated@example.com"),
+ Optional.of("https://linkedin.com/in/updated"),
+ Optional.of("Updated intro"),
+ Optional.of("Mentor"),
+ Optional.of("Tech"),
+ Optional.of("Engineer"),
+ Optional.of("AI,ML"),
+ Optional.of("Notes"));
final Member existingMember = Member.builder()
.id(id)
@@ -241,16 +259,16 @@ void updateMember_successWithAllFields() {
final Member updatedMember = Member.builder()
.id(id)
- .firstName(request.firstName())
- .lastName(request.lastName())
- .email(request.email())
- .linkedInUrl(request.linkedInUrl())
- .introduction(request.introduction())
- .matchPref(request.matchPref())
- .industryPref(request.industryPref())
- .rolePref(request.rolePref())
- .topics(request.topics())
- .extraNotes(request.extraNotes())
+ .firstName("UpdatedFirstName")
+ .lastName("UpdatedLastName")
+ .email("updated@example.com")
+ .linkedInUrl("https://linkedin.com/in/updated")
+ .introduction("Updated intro")
+ .matchPref("Mentor")
+ .industryPref("Tech")
+ .rolePref("Engineer")
+ .topics("AI,ML")
+ .extraNotes("Notes")
.build();
when(memberRepo.getMemberById(id)).thenReturn(Optional.of(existingMember));
@@ -258,15 +276,86 @@ void updateMember_successWithAllFields() {
final MemberDto response = memberService.updateMember(request, id);
- assertEquals(request.firstName(), response.getFirstName());
- assertEquals(request.lastName(), response.getLastName());
- assertEquals(request.email(), response.getEmail());
- assertEquals(request.linkedInUrl(), response.getLinkedInUrl());
- assertEquals(request.introduction(), response.getIntroduction());
- assertEquals(request.matchPref(), response.getMatchPref());
- assertEquals(request.industryPref(), response.getIndustryPref());
- assertEquals(request.rolePref(), response.getRolePref());
- assertEquals(request.topics(), response.getTopics());
- assertEquals(request.extraNotes(), response.getExtraNotes());
+ assertEquals("UpdatedFirstName", response.getFirstName());
+ assertEquals("UpdatedLastName", response.getLastName());
+ assertEquals("updated@example.com", response.getEmail());
+ assertEquals("https://linkedin.com/in/updated", response.getLinkedInUrl());
+ assertEquals("Updated intro", response.getIntroduction());
+ assertEquals("Mentor", response.getMatchPref());
+ assertEquals("Tech", response.getIndustryPref());
+ assertEquals("Engineer", response.getRolePref());
+ assertEquals("AI,ML", response.getTopics());
+ assertEquals("Notes", response.getExtraNotes());
+ }
+
+ @Test
+ void updateMember_throwsExceptionWhenEmailIsDuplicate() {
+ final UUID id = UUID.randomUUID();
+ final UUID otherMemberId = UUID.randomUUID();
+ final UpdateMemberRequest request = new UpdateMemberRequest(
+ Optional.empty(),
+ Optional.empty(),
+ Optional.of("existing@example.com"),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty());
+
+ final Member existingMember = Member.builder()
+ .id(id)
+ .firstName("John")
+ .lastName("Doe")
+ .email("john@example.com")
+ .build();
+
+ final Member otherMember =
+ Member.builder().id(otherMemberId).email("existing@example.com").build();
+
+ when(memberRepo.getMemberById(id)).thenReturn(Optional.of(existingMember));
+ when(memberRepo.getMemberByEmail("existing@example.com")).thenReturn(Optional.of(otherMember));
+
+ assertThrows(MemberDuplicateException.class, () -> memberService.updateMember(request, id));
+ verify(memberRepo, never()).updateMember(any());
+ }
+
+ @Test
+ void updateMember_successWhenUpdatingWithSameEmail() {
+ final UUID id = UUID.randomUUID();
+ final UpdateMemberRequest request = new UpdateMemberRequest(
+ Optional.empty(),
+ Optional.empty(),
+ Optional.of("john@example.com"),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty());
+
+ final Member existingMember = Member.builder()
+ .id(id)
+ .firstName("John")
+ .lastName("Doe")
+ .email("john@example.com")
+ .linkedInUrl("https://linkedin.com/in/john")
+ .introduction("intro")
+ .matchPref("Mentor")
+ .industryPref("Tech")
+ .rolePref("Engineer")
+ .topics("AI")
+ .extraNotes("notes")
+ .build();
+
+ when(memberRepo.getMemberById(id)).thenReturn(Optional.of(existingMember));
+ when(memberRepo.updateMember(any())).thenReturn(Optional.of(existingMember));
+
+ final MemberDto response = memberService.updateMember(request, id);
+
+ assertEquals("john@example.com", response.getEmail());
+ verify(memberRepo, never()).getMemberByEmail(any());
}
}