Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

import com.climingo.climingoApi.global.auth.RequestMember;
import com.climingo.climingoApi.member.api.request.UpdateNicknameRequest;
import com.climingo.climingoApi.member.api.request.UpdatePhysicalinfo;
import com.climingo.climingoApi.member.api.response.MemberInfoResponse;
import com.climingo.climingoApi.member.application.MemberService;
import com.climingo.climingoApi.member.domain.Member;
import com.climingo.climingoApi.member.domain.PhysicalInfo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
Expand Down Expand Up @@ -40,6 +42,14 @@ public ResponseEntity<MemberInfoResponse> findMemberInfo(@PathVariable(value = "
return ResponseEntity.ok().body(memberInfoResponse);
}

@PatchMapping("/member/{memberId}")
public ResponseEntity<Void> updatePhysicalInfo(@RequestMember Member member, @PathVariable(value = "memberId") Long memberId,
@RequestBody @Valid UpdatePhysicalinfo request){
memberService.updatePhysicalInfo(member,memberId,request.getPhysicalInfo());
return ResponseEntity.ok().build();

}

@PatchMapping("/members/{memberId}/nickname")
@Operation(summary = "멤버 닉네임 변경", description = "회원정보 닉네임 변경")
@Parameter(name = "nickname", description = "변경할 닉네임")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.climingo.climingoApi.member.api.request;

import com.climingo.climingoApi.member.domain.PhysicalInfo;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.Builder;
import lombok.Getter;

@Builder
@Getter
@JsonDeserialize(builder = UpdatePhysicalinfo.UpdatePhysicalinfoBuilder.class)
public class UpdatePhysicalinfo {

@NotNull(message = "physicalInfo는 필수입니다")
@Valid
@JsonProperty("physicalInfo")
private final PhysicalInfo physicalInfo;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.climingo.climingoApi.member.api.response;

import com.climingo.climingoApi.member.domain.Member;
import com.climingo.climingoApi.member.domain.PhysicalInfo;
import lombok.Getter;

@Getter
Expand All @@ -11,13 +12,15 @@ public class MemberInfoResponse {
private String email;
private String profileUrl;
private String providerType;
private PhysicalInfo physicalInfo;

public MemberInfoResponse(Member member) {
this.memberId = member.getId();
this.nickname = member.getNickname();
this.email = member.getEmail();
this.profileUrl = member.getProfileUrl();
this.providerType = member.getProviderType();
this.physicalInfo = member.getPhysicalInfo();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@

import com.climingo.climingoApi.member.api.response.MemberInfoResponse;
import com.climingo.climingoApi.member.domain.Member;
import com.climingo.climingoApi.member.domain.PhysicalInfo;
import org.springframework.transaction.annotation.Transactional;

public interface MemberService {

MemberInfoResponse findMemberInfo(Long memberId);

void updateNickname(Member member, Long memberId, String nickname);

void updatePhysicalInfo(Member member, Long memberId, PhysicalInfo physicalInfo);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.climingo.climingoApi.member.api.response.MemberInfoResponse;
import com.climingo.climingoApi.member.domain.Member;
import com.climingo.climingoApi.member.domain.MemberRepository;
import com.climingo.climingoApi.member.domain.PhysicalInfo;
import jakarta.persistence.EntityNotFoundException;
import java.util.NoSuchElementException;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -93,4 +94,15 @@ private boolean isDuplicated(String nickname) {
return memberRepository.existsByNickname(nickname);
}


@Override
@Transactional
public void updatePhysicalInfo(Member member, Long memberId, PhysicalInfo physicalInfo){



member.updatePhysicalinfo(physicalInfo);
memberRepository.save(member);
}

}
10 changes: 10 additions & 0 deletions src/main/java/com/climingo/climingoApi/member/domain/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,16 @@ public void updateNickname(String nickname) {
this.nickname = nickname;
}

// 소수점 관련 처리 로직
public boolean isPhysicalinfoDouble(PhysicalInfo physicalInfo){

return true;
}

public void updatePhysicalinfo(PhysicalInfo physicalInfo){
this.physicalInfo = physicalInfo;
}

public boolean isSameMember(Member member) {
return this.isSameMember(member.getId());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,20 @@
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;

@Embeddable
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class PhysicalInfo {

@JsonProperty("height")
private Double height;
private BigDecimal height;

@JsonProperty("weight")
private Double weight;
private BigDecimal weight;

@JsonProperty("armSpan")
private Double armSpan;
private BigDecimal armSpan;
}
4 changes: 2 additions & 2 deletions src/main/resources/application-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ oauth2:
path: ENC(UQVIwN53EBnE0TcwgTT79HYM2KUrEcRDhlx72+W+anbO87WiWJbCSJQKOPqGBNGaze2oi9obarWiRuzW5UdJtLUzNe7icfKNPepJrHZXhVSD5UUO5eHsvbFVeWhVScgeIPxvtbdsk0LMsLH7EbzSuC852HHy0/KQ6j8gJREw/RkyFuHFYJR2VupYASz9jpdabzwA6q9Lh9v7BPqLB2nFzub5xmHMU7HWICvzSHbnTsgXqyQXr7XtvHxQVZN5z4lEAKbk0BB53vm3ammzkxhTo656ZFAgK69n)

ffmpeg:
ffmpeg-path: /usr/local/bin/ffmpeg
ffprobe-path: /usr/local/bin/ffprobe
ffmpeg-path: /opt/homebrew/bin/ffmpeg
ffprobe-path: /opt/homebrew/bin/ffprobe

app:
version: local_temp_version
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package com.climingo.climingoApi.member.api;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.climingo.climingoApi.global.auth.RequestMember;
import com.climingo.climingoApi.global.exception.GlobalExceptionHandler;
import com.climingo.climingoApi.member.api.response.MemberInfoResponse;
import com.climingo.climingoApi.member.application.MemberService;
import com.climingo.climingoApi.member.domain.Member;
import com.climingo.climingoApi.member.domain.PhysicalInfo;
import com.climingo.climingoApi.member.domain.UserRole;
import com.climingo.climingoApi.message.error.ErrorAlertMessageProvider;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.persistence.EntityNotFoundException;
import java.math.BigDecimal;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

@DisplayName("MemberController 단위 테스트")
class MemberControllerTest {

private MockMvc mockMvc;
private MemberService memberService;
private ObjectMapper objectMapper;
private Member loginMember;

@BeforeEach
void setUp() {
memberService = mock(MemberService.class);
objectMapper = new ObjectMapper();

loginMember = Member.builder()
.id(1L)
.authId("auth123")
.providerType("kakao")
.nickname("testUser")
.email("test@test.com")
.profileUrl("http://profile.url")
.physicalInfo(new PhysicalInfo(new BigDecimal("175.5"), new BigDecimal("70.0"), new BigDecimal("180.0")))
.role(UserRole.USER)
.build();

HandlerMethodArgumentResolver requestMemberResolver = new HandlerMethodArgumentResolver() {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterAnnotation(RequestMember.class) != null
&& Member.class.equals(parameter.getParameterType());
}

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
return loginMember;
}
};

mockMvc = MockMvcBuilders.standaloneSetup(new MemberController(memberService))
.setCustomArgumentResolvers(requestMemberResolver)
.setControllerAdvice(new GlobalExceptionHandler(mock(ErrorAlertMessageProvider.class)))
.build();
}

@Test
@DisplayName("GET /members - 로그인한 회원이 자신의 정보를 정상 조회한다")
void findMyInfo_success() throws Exception {
MemberInfoResponse response = new MemberInfoResponse(loginMember);
when(memberService.findMemberInfo(eq(1L))).thenReturn(response);

mockMvc.perform(get("/members"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.memberId").value(1L))
.andExpect(jsonPath("$.nickname").value("testUser"))
.andExpect(jsonPath("$.email").value("test@test.com"))
.andExpect(jsonPath("$.profileUrl").value("http://profile.url"))
.andExpect(jsonPath("$.providerType").value("kakao"))
.andExpect(jsonPath("$.physicalInfo.height").value(175.5))
.andExpect(jsonPath("$.physicalInfo.weight").value(70.0))
.andExpect(jsonPath("$.physicalInfo.armSpan").value(180.0));
}

@Test
@DisplayName("GET /members - 존재하지 않는 회원 조회 시 EntityNotFoundException이 발생한다")
void findMyInfo_notFound() throws Exception {
when(memberService.findMemberInfo(eq(1L)))
.thenThrow(new EntityNotFoundException("id가 1인 회원은 존재하지 않습니다"));

mockMvc.perform(get("/members"))
.andExpect(status().isNotFound());
}

@Test
@DisplayName("PATCH /member/{memberId} - 유효한 요청으로 신체 정보를 업데이트한다")
void updatePhysicalInfo_success() throws Exception {
doNothing().when(memberService).updatePhysicalInfo(any(Member.class), eq(1L), any(PhysicalInfo.class));

String requestBody = objectMapper.writeValueAsString(
java.util.Map.of("physicalInfo", java.util.Map.of(
"height", 180.0,
"weight", 75.5,
"armSpan", 185.0
))
);

mockMvc.perform(patch("/member/{memberId}", 1L)
.contentType(MediaType.APPLICATION_JSON)
.content(requestBody))
.andExpect(status().isOk());
}

@Test
@DisplayName("PATCH /member/{memberId} - physicalInfo 없이 호출 시 400 에러가 발생한다")
void updatePhysicalInfo_missingBody() throws Exception {
mockMvc.perform(patch("/member/{memberId}", 1L)
.contentType(MediaType.APPLICATION_JSON)
.content("{}"))
.andExpect(status().isBadRequest());
}
}