Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

회원 정보 조회 기능 구현 #137

Merged
merged 10 commits into from
Jul 27, 2023
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.votogether.domain.member.controller;

import com.votogether.domain.member.dto.MemberInfoResponse;
import com.votogether.domain.member.entity.Member;
import com.votogether.domain.member.service.MemberService;
import com.votogether.global.jwt.Auth;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "회원", description = "회원 API")
@RequiredArgsConstructor
@RequestMapping("/members")
@RestController
public class MemberController {

private final MemberService memberService;

@Operation(summary = "회원 정보 조회", description = "회원 정보를 반환한다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "정보 조회 성공"),
@ApiResponse(responseCode = "400", description = "올바르지 않은 요청"),
})
@GetMapping("/me")
public ResponseEntity<MemberInfoResponse> findMemberInfo(@Auth final Member member) {
return ResponseEntity.ok(memberService.findMemberInfo(member));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.votogether.domain.member.dto;

public record MemberInfoResponse(
String nickname,
Integer point,
int postCount,
int voteCount
) {
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.votogether.domain.member.service;

import com.votogether.domain.member.dto.MemberInfoResponse;
import com.votogether.domain.member.entity.Member;
import com.votogether.domain.member.repository.MemberRepository;
import com.votogether.domain.post.repository.PostRepository;
import com.votogether.domain.vote.repository.VoteRepository;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
Expand All @@ -12,6 +15,8 @@
public class MemberService {

private final MemberRepository memberRepository;
private final PostRepository postRepository;
private final VoteRepository voteRepository;

@Transactional
public Member register(final Member member) {
Expand All @@ -28,4 +33,17 @@ public Member findById(final Long memberId) {
.orElseThrow(() -> new IllegalArgumentException("해당 Id를 가진 회원은 존재하지 않습니다."));
}

@Transactional(readOnly = true)
public MemberInfoResponse findMemberInfo(final Member member) {
final int numberOfPosts = postRepository.countByMember(member);
final int numberOfVotes = voteRepository.countByMember(member);

return new MemberInfoResponse(
member.getNickname(),
member.getPoint(),
numberOfPosts,
numberOfVotes
);
}

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package com.votogether.domain.post.repository;

import com.votogether.domain.member.entity.Member;
import com.votogether.domain.post.entity.Post;
import org.springframework.data.jpa.repository.JpaRepository;

public interface PostRepository extends JpaRepository<Post, Long> {

int countByMember(final Member member);

}
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,6 @@ List<VoteStatus> findVoteCountByPostOptionIdGroupByAgeRangeAndGender(

List<Vote> findByMemberAndPostOptionIn(final Member member, final List<PostOption> postOptions);

int countByMember(final Member member);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.votogether.domain.member.controller;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;

import com.votogether.domain.member.dto.MemberInfoResponse;
import com.votogether.domain.member.entity.Gender;
import com.votogether.domain.member.entity.Member;
import com.votogether.domain.member.entity.SocialType;
import com.votogether.domain.member.service.MemberService;
import com.votogether.global.jwt.TokenProcessor;
import io.restassured.module.mockmvc.RestAssuredMockMvc;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpHeaders;

@Import(TokenProcessor.class)
@WebMvcTest(MemberController.class)
class MemberControllerTest {

@MockBean
MemberService memberService;

@Autowired
TokenProcessor tokenProcessor;

@BeforeEach
void setUp() {
RestAssuredMockMvc.standaloneSetup(new MemberController(memberService));
}

@Test
@DisplayName("회원 정보를 조회한다.")
void findMemberInfo() {
// given
Member member = Member.builder()
.nickname("저문")
.gender(Gender.MALE)
.ageRange("20~29")
.birthday("0101")
.socialType(SocialType.KAKAO)
.socialId("123123")
.point(1234)
.build();

MemberInfoResponse memberInfoResponse = new MemberInfoResponse(
"저문",
1234,
0,
0
);

given(memberService.register(member)).willReturn(member);
Member registeredMember = memberService.register(member);
String token = tokenProcessor.generateToken(registeredMember);

given(memberService.findMemberInfo(any(Member.class))).willReturn(memberInfoResponse);

// when
MemberInfoResponse response = RestAssuredMockMvc
.given().log().all()
.headers(HttpHeaders.AUTHORIZATION, "Bearer " + token)
.when().get("/members/me")
.then().log().all()
.extract()
.as(MemberInfoResponse.class);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2
맨 위의 3줄이랑 //when 에서 헤더에 토큰 값 설정해주는 코드가 없어도 테스트가 통과하더라구요
@WebMvcTest에서는 알규먼트리졸브랑 필터가 안사용되어지는거 같은데 어떡하면 좋을까요

.+ MemberService계층에 대한 테스트가 추가되면 좋을거같아요

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

와우... 처음 알았어요...! 루쿠신...
엄청난걸 배워가네요!
삭제하면 좋을 것 같네요.
추가적으로 MemberService에 대한 테스트는 repository에 있는 내용을 단순 response객체에 담아주는 역할 밖에 없어서 따로 작성하지 않았어요. 이에 대해 루쿠는 어떻게 생각하시는지 궁금해요!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋아요~ repository에 대한 테스트도 있으니 충분한거같습니다


// then
assertAll(
() -> assertThat(response.nickname()).isEqualTo("저문"),
() -> assertThat(response.point()).isEqualTo(1234),
() -> assertThat(response.postCount()).isEqualTo(0),
() -> assertThat(response.voteCount()).isEqualTo(0)
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
import static org.assertj.core.api.Assertions.assertThat;

import com.votogether.RepositoryTest;
import com.votogether.domain.member.entity.Gender;
import com.votogether.domain.member.entity.Member;
import com.votogether.domain.member.entity.SocialType;
import com.votogether.domain.member.repository.MemberRepository;
import com.votogether.domain.post.entity.Post;
import com.votogether.domain.post.entity.PostBody;
import com.votogether.fixtures.MemberFixtures;
import java.time.LocalDateTime;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
Expand All @@ -17,30 +18,45 @@
class PostRepositoryTest {

@Autowired
PostRepository postRepository;
private PostRepository postRepository;

@Autowired
MemberRepository memberRepository;
private MemberRepository memberRepository;

@Test
@DisplayName("Post를 저장한다")
void save() {
// given
Member member = memberRepository.save(MemberFixtures.MALE_EARLY_10.get());

PostBody postBody = PostBody.builder()
final PostBody postBody = PostBody.builder()
.title("title")
.content("content")
.build();

Post post = Post.builder()
final Member member = Member.builder()
.gender(Gender.MALE)
.point(0)
.socialType(SocialType.KAKAO)
.nickname("user1")
.gender(Gender.MALE)
.birthday("0718")
.ageRange("10~14")
.socialType(SocialType.KAKAO)
.socialId("[email protected]")
.ageRange("30~39")
.birthday("0101")
.point(0)
.build();

final Post post = Post.builder()
.member(member)
.postBody(postBody)
.deadline(LocalDateTime.of(2100, 7, 12, 0, 0))
.build();

memberRepository.save(member);

// when
Post savedPost = postRepository.save(post);
final Post savedPost = postRepository.save(post);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3
해당 테스트에서 final키워드가 몇군데추가되었어요~

Copy link
Collaborator Author

@jeomxon jeomxon Jul 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 아벨의 코드인데 아마 conflict해결하면서 파일 자체가 포함된 것 같네요.
제가 따로 수정하지 않고 나중에 아벨이 post에 대한 테스트 수정하시면서 한꺼번에 하는게 더 좋아보여서 그대로 두겠습니다~

conflict를 잘못 해결했는지 제가 작성한 테스트가 날아가버렸네요..ㅠㅠ 수정해서 추가할게요..!


// then
assertThat(savedPost).isNotNull();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,4 +252,39 @@ void findVoteCountByPostOptionIdGroupByAgeRangeAndGender() {
);
}

@Test
@DisplayName("해당 회원이 투표한 개수를 반환한다.")
void countByMember() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q
해당 회원이 작성한 게시글을 반환하는 테스트는 없나요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 회원이 작성한 게시글 수를 검증하는 테스트를 말씀하신거라면
위에 적은 것처럼 conflict해결하다가 날려먹은 것 같네요... 추가하겠습니다!

// given
Member member = memberRepository.save(MemberFixtures.MALE_LATE_10.get());
Member writer = memberRepository.save(MemberFixtures.MALE_20.get());
Post post = postRepository.save(
Post.builder()
.member(writer)
.postBody(PostBody.builder().title("title").content("content").build())
.deadline(LocalDateTime.of(2100, 7, 12, 0, 0))
.build()
);
PostOption postOption = postOptionRepository.save(
PostOption.builder()
.post(post)
.sequence(1)
.content("치킨")
.build()
);
Vote vote = Vote.builder()
.member(member)
.postOption(postOption)
.build();

memberRepository.save(member);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3
저장된 멤버를 다시 저장하고있어요~

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

헉.. 그러네요 수정했습니다~

voteRepository.save(vote);

// when
int numberOfVote = voteRepository.countByMember(member);

// then
assertThat(numberOfVote).isEqualTo(1);
}

}