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

(회원) 선호 카테고리 삭제 기능 구현 #79

Merged
merged 13 commits into from
Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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 @@ -10,6 +10,7 @@
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
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;
Expand All @@ -28,7 +29,7 @@ public class CategoryController {
@ApiResponse(responseCode = "200", description = "조회 성공")
@GetMapping("/guest")
public ResponseEntity<List<CategoryResponse>> getAllCategories() {
List<CategoryResponse> categories = categoryService.getAllCategories();
final List<CategoryResponse> categories = categoryService.getAllCategories();
return ResponseEntity.status(HttpStatus.OK).body(categories);
}

Expand All @@ -40,4 +41,12 @@ public ResponseEntity<Void> addFavoriteCategory(final Member member, @PathVariab
return ResponseEntity.status(HttpStatus.CREATED).build();
}

@Operation(summary = "선호 카테고리 삭제하기", description = "선호하는 카테고리를 선호 카테고리 목록에서 삭제한다.")
@ApiResponse(responseCode = "204", description = "삭제 성공")
Copy link
Collaborator

Choose a reason for hiding this comment

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

P2 : 해당 카테고리가 존재하지 않을 때처럼 예외 상황에 대한 응답도 설명하는게 좋을 것 같아요 :)

@DeleteMapping("/{categoryId}/like")
public ResponseEntity<Void> removeFavoriteCategory(final Member member, @PathVariable final Long categoryId) {
categoryService.removeFavoriteCategory(member, categoryId);
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class CategoryService {

@Transactional(readOnly = true)
public List<CategoryResponse> getAllCategories() {
List<Category> categories = categoryRepository.findAll();
final List<Category> categories = categoryRepository.findAll();

return categories.stream()
.map(CategoryResponse::new)
Expand All @@ -29,18 +29,29 @@ public List<CategoryResponse> getAllCategories() {

@Transactional
public void addFavoriteCategory(final Member member, final Long categoryId) {
Category category = categoryRepository.findById(categoryId)
final Category category = categoryRepository.findById(categoryId)
.orElseThrow(() -> new IllegalArgumentException("해당 카테고리가 존재하지 않습니다."));

memberCategoryRepository.findByMemberAndCategory(member, category)
.ifPresent(ignore -> new IllegalStateException("이미 선호 카테고리에 등록되어 있습니다."));

MemberCategory memberCategory = MemberCategory.builder()
final MemberCategory memberCategory = MemberCategory.builder()
.member(member)
.category(category)
.build();

memberCategoryRepository.save(memberCategory);
}

@Transactional
public void removeFavoriteCategory(final Member member, final Long categoryId) {
final Category category = categoryRepository.findById(categoryId)
.orElseThrow(() -> new IllegalArgumentException("해당 카테고리가 존재하지 않습니다."));

final MemberCategory memberCategory = memberCategoryRepository.findByMemberAndCategory(member, category)
.orElseThrow(() -> new IllegalArgumentException("해당 카테고리는 선호 카테고리가 아닙니다."));
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.

어 좋아요~ 수정하겠습니다


memberCategoryRepository.delete(memberCategory);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,21 @@ public void vote(
final Long postId,
final Long postOptionId
) {
Post post = postRepository.findById(postId)
final Post post = postRepository.findById(postId)
.orElseThrow(() -> new IllegalArgumentException("해당 게시글이 존재하지 않습니다."));

validateAlreadyVoted(member, post);

PostOption postOption = postOptionRepository.findById(postOptionId)
final PostOption postOption = postOptionRepository.findById(postOptionId)
.orElseThrow(() -> new IllegalArgumentException("해당 선택지가 존재하지 않습니다."));

Vote vote = post.makeVote(member, postOption);
final Vote vote = post.makeVote(member, postOption);
member.plusPoint(1);
voteRepository.save(vote);
}

private void validateAlreadyVoted(Member member, Post post) {

private void validateAlreadyVoted(final Member member, final Post post) {
final PostOptions postOptions = post.getPostOptions();
final List<Vote> alreadyVoted = voteRepository.findByMemberAndPostOptionIn(member, postOptions.getPostOptions());
if (!alreadyVoted.isEmpty()) {
Expand All @@ -54,20 +55,20 @@ public void changeVote(
final Long originPostOptionId,
final Long newPostOptionId
) {
Post post = postRepository.findById(postId)
final Post post = postRepository.findById(postId)
.orElseThrow(() -> new IllegalArgumentException("해당 게시글이 존재하지 않습니다."));

PostOption originPostOption = postOptionRepository.findById(originPostOptionId)
final PostOption originPostOption = postOptionRepository.findById(originPostOptionId)
.orElseThrow(() -> new IllegalArgumentException("헤당 선택지가 존재하지 않습니다."));

Vote originVote = voteRepository.findByMemberAndPostOption(member, originPostOption)
final Vote originVote = voteRepository.findByMemberAndPostOption(member, originPostOption)
.orElseThrow(() -> new IllegalArgumentException("선택지에 해당되는 투표가 존재하지 않습니다."));

PostOption newPostOption = postOptionRepository.findById(newPostOptionId)
final PostOption newPostOption = postOptionRepository.findById(newPostOptionId)
.orElseThrow(() -> new IllegalArgumentException("헤당 선택지가 존재하지 않습니다."));

voteRepository.delete(originVote);
Vote vote = post.makeVote(member, newPostOption);
final Vote vote = post.makeVote(member, newPostOption);
voteRepository.save(vote);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.votogether.domain.category.contorller;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.doNothing;

import com.votogether.domain.category.dto.response.CategoryResponse;
import com.votogether.domain.category.entity.Category;
import com.votogether.domain.category.service.CategoryService;
import io.restassured.module.mockmvc.RestAssuredMockMvc;
import java.util.List;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpStatus;

@WebMvcTest(CategoryController.class)
class CategoryControllerTest {

@MockBean
CategoryService categoryService;

@BeforeEach
void setUp() {
RestAssuredMockMvc.standaloneSetup(new CategoryController(categoryService));
}

@Test
@DisplayName("전체 카테고리 목록을 조회한다.")
void getAllCategories() {
// given
Category category = Category.builder()
.name("개발")
.build();
given(categoryService.getAllCategories()).willReturn(List.of(new CategoryResponse(category)));

// when
RestAssuredMockMvc.
given().log().all()
.when().get("/categories/guest")
.then().log().all()
.status(HttpStatus.OK)
.body("[0].id", Matchers.nullValue())
.body("[0].name", Matchers.equalTo("개발"))
.body("[0].isFavorite", Matchers.equalTo(false));
Copy link
Collaborator

Choose a reason for hiding this comment

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

P3
Matchers를 static import로 가져가도 좋을 것 같아요!

}

@Test
@DisplayName("선호하는 카테고리를 선호 카테고리 목록에 추가한다.")
void addFavoriteCategory() {
// given
doNothing().when(categoryService).addFavoriteCategory(any(), any());
Copy link
Collaborator

Choose a reason for hiding this comment

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

Q
doNothing().when()은 어떤 역할을 하나요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

목 객체 서비스가 해당 메서드를 실행하면 아무것도 응답하지 않는 역할을 합니다


// when & then
RestAssuredMockMvc.
given().log().all()
.when().post("/categories/1/like")
.then().log().all()
.status(HttpStatus.CREATED);
}

@Test
@DisplayName("선호 카테고리를 삭제한다.")
void removeFavoriteCategory() {
// given
doNothing().when(categoryService).removeFavoriteCategory(any(), any());

// when & then
RestAssuredMockMvc.
given().log().all()
.when().delete("/categories/1/like")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
.when().delete("/categories/1/like")
.when().delete("/categories/{categoryId}/like", 1)

P2 : 위와 같은 표현을 사용해도 동일한 동작을 해서 위와 같이 표현해보는건 어떨까요?

.then().log().all()
.status(HttpStatus.NO_CONTENT);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package com.votogether.domain.category.service;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;

import com.votogether.domain.category.dto.response.CategoryResponse;
import com.votogether.domain.category.entity.Category;
import com.votogether.domain.category.repository.CategoryRepository;
import com.votogether.domain.member.entity.Gender;
import com.votogether.domain.member.entity.Member;
import com.votogether.domain.member.entity.MemberCategory;
import com.votogether.domain.member.entity.SocialType;
import com.votogether.domain.member.repository.MemberCategoryRepository;
import com.votogether.domain.member.repository.MemberRepository;
import java.time.LocalDateTime;
import java.util.List;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;

@SpringBootTest
@Transactional
class CategoryServiceTest {

@Autowired
CategoryService categoryService;

@Autowired
CategoryRepository categoryRepository;

@Autowired
MemberCategoryRepository memberCategoryRepository;

@Autowired
MemberRepository memberRepository;

@Test
@DisplayName("모든 카테고리를 가져온다.")
void getAllCategories() {
// given
Category category = Category.builder()
.name("개발")
.build();

categoryRepository.save(category);

// when
List<CategoryResponse> categories = categoryService.getAllCategories();

// then
assertAll(
() -> assertThat(categories.get(0).id()).isNotNull(),
() -> assertThat(categories.get(0).name()).isEqualTo("개발"),
() -> assertThat(categories.get(0).isFavorite()).isFalse()
);
}

@Test
@DisplayName("선호하는 카테고리를 선호 카테고리 목록에 추가한다.")
void addFavoriteCategory() {
// given
Category category = Category.builder()
.name("개발")
.build();

Member member = Member.builder()
.gender(Gender.MALE)
.point(0)
.socialType(SocialType.GOOGLE)
.nickname("user1")
.socialId("kakao@gmail.com")
.birthDate(
LocalDateTime.of(1995, 7, 12, 0, 0))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
.birthDate(
LocalDateTime.of(1995, 7, 12, 0, 0))
.birthDate(LocalDateTime.of(1995, 7, 12, 0, 0))

P2 : 한줄로 해도 좋을 것 같아요 :)

.build();

categoryRepository.save(category);
memberRepository.save(member);

Long categoryId = category.getId();

// when
categoryService.addFavoriteCategory(member, categoryId);

// then
MemberCategory memberCategory = memberCategoryRepository.findByMemberAndCategory(member, category).get();

Assertions.assertAll(
Copy link
Collaborator

Choose a reason for hiding this comment

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

P1 : static import 해주시면 감사합니다!

() -> assertThat(memberCategory.getMember()).isSameAs(member),
() -> assertThat(memberCategory.getCategory()).isSameAs(category)
);
}

@Test
@DisplayName("선호하는 카테고리를 선호 카테고리 목록에 삭제한다.")
Copy link
Collaborator

Choose a reason for hiding this comment

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

P1 : 카테고리가 존재하지 않을 때 예외가 발생하는 테스트도 있으면 좋겠습니다 :)

void removeFavoriteCategory() {
// given
Category category = Category.builder()
.name("개발")
.build();

Member member = Member.builder()
.gender(Gender.MALE)
.point(0)
.socialType(SocialType.GOOGLE)
.nickname("user1")
.socialId("kakao@gmail.com")
.birthDate(
LocalDateTime.of(1995, 7, 12, 0, 0))
.build();

MemberCategory memberCategory = MemberCategory.builder()
.member(member)
.category(category)
.build();

categoryRepository.save(category);
memberRepository.save(member);
memberCategoryRepository.save(memberCategory);

Long categoryId = category.getId();

// when
categoryService.removeFavoriteCategory(member, categoryId);

// then
boolean isEmpty = memberCategoryRepository.findByMemberAndCategory(member, category).isEmpty();
assertThat(isEmpty).isTrue();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
boolean isEmpty = memberCategoryRepository.findByMemberAndCategory(member, category).isEmpty();
assertThat(isEmpty).isTrue();
Optional<MemberCategory> findedMemberCategory = memberCategoryRepository.findByMemberAndCategory(member, category);
assertThat(findedMemberCategory).isEmpty();

P2 : 가져오는 주체가 MemberCategory이기에 이를 더 드러내보는건 어떨까요?

}

}