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

태그 매칭률 조회 기능 추가 #104

Merged
merged 3 commits into from
Nov 16, 2023
Merged
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
20 changes: 18 additions & 2 deletions src/docs/asciidoc/api-doc.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,23 @@ include::{snippets}/user-controller-test/respond_200_when_delete_user_follow_suc
| `404 NOT FOUND` | `NOT_FOUND_USER_FOLLOW` | 해당하는 팔로우가 없는 경우
|===

=== 1-9. 특정 회원의 좋아요 상품 목록 조회
=== 1-9. 태그 매칭률 조회
==== Path Parameters
include::{snippets}/user-controller-test/respond_200_when_read_user_tag_match_percentage_successfully/path-parameters.adoc[]
==== Sample Request
include::{snippets}/user-controller-test/respond_200_when_read_user_tag_match_percentage_successfully/http-request.adoc[]
==== Response Fields
include::{snippets}/user-controller-test/respond_200_when_read_user_tag_match_percentage_successfully/response-fields.adoc[]
==== Sample Response
include::{snippets}/user-controller-test/respond_200_when_read_user_tag_match_percentage_successfully/http-response.adoc[]
==== Error Response
|===
| HTTP Status | Error Code | Detail

| `404 NOT FOUND` | `NOT_FOUND_USER` | 해당하는 유저가 없는 경우
|===

=== 1-10. 특정 회원의 좋아요 상품 목록 조회
==== Path Parameters
include::{snippets}/user-controller-test/respond_200_when_read_liked_product_list_successfully/path-parameters.adoc[]
==== Request Fields
Expand All @@ -120,7 +136,7 @@ include::{snippets}/user-controller-test/respond_200_when_read_liked_product_lis
==== Sample Response
include::{snippets}/user-controller-test/respond_200_when_read_liked_product_list_successfully/http-response.adoc[]

=== 1-10. 특정 회원의 북마크 상품 목록 조회
=== 1-11. 특정 회원의 북마크 상품 목록 조회
==== Path Parameters
include::{snippets}/user-controller-test/respond_200_when_read_bookmarked_product_list_successfully/path-parameters.adoc[]
==== Request Fields
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/com/cvsgo/controller/UserController.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ public SuccessResponse<Void> deleteUserFollow(@LoginUser User user, @PathVariabl
return SuccessResponse.create();
}

@GetMapping("/users/{userId}/tag-match-percentage")
public SuccessResponse<Integer> readUserTagMatchPercentage(
@LoginUser User user, @PathVariable Long userId) {
return SuccessResponse.from(userService.readUserTagMatchPercentage(user, userId));
}

@GetMapping("/users/{userId}/liked-products")
public SuccessResponse<Page<ReadProductResponseDto>> readLikedProductList(
@PathVariable Long userId, @ModelAttribute ReadUserProductRequestDto request,
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/com/cvsgo/repository/UserTagRepository.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.cvsgo.repository;

import com.cvsgo.entity.User;
import com.cvsgo.entity.UserTag;
import java.util.List;
import org.springframework.data.jpa.repository.EntityGraph;
Expand All @@ -10,4 +11,6 @@ public interface UserTagRepository extends JpaRepository<UserTag, Long> {
@EntityGraph(attributePaths = {"user", "tag"})
List<UserTag> findByUserIdIn(List<Long> userIds);

List<UserTag> findAllByUser(User user);

}
31 changes: 31 additions & 0 deletions src/main/java/com/cvsgo/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@
import com.cvsgo.entity.Tag;
import com.cvsgo.entity.User;
import com.cvsgo.entity.UserFollow;
import com.cvsgo.entity.UserTag;
import com.cvsgo.exception.BadRequestException;
import com.cvsgo.exception.DuplicateException;
import com.cvsgo.exception.NotFoundException;
import com.cvsgo.repository.ReviewRepository;
import com.cvsgo.repository.TagRepository;
import com.cvsgo.repository.UserFollowRepository;
import com.cvsgo.repository.UserRepository;
import com.cvsgo.repository.UserTagRepository;
import jakarta.persistence.EntityManager;
import java.util.List;
import lombok.RequiredArgsConstructor;
Expand All @@ -36,6 +38,8 @@
@RequiredArgsConstructor
public class UserService {

private final UserTagRepository userTagRepository;

private final UserRepository userRepository;

private final TagRepository tagRepository;
Expand Down Expand Up @@ -177,4 +181,31 @@ public void deleteUserFollow(User user, Long userId) {
userFollowRepository.delete(userFollow);
}

/**
* 태그 매칭률을 조회한다.
*
* @param user 로그인한 사용자
* @param userId 태그 매칭률을 조회할 사용자 ID
* @return 태그 매칭률
* @throws NotFoundException 해당하는 아이디를 가진 사용자가 없는 경우
*/
@Transactional(readOnly = true)
public Integer readUserTagMatchPercentage(User user, Long userId) {
User targetUser = userRepository.findById(userId).orElseThrow(() -> NOT_FOUND_USER);

List<Tag> loginUserTag = userTagRepository.findAllByUser(user).stream().map(UserTag::getTag)
.toList();
List<Tag> targetUserTag = userTagRepository.findAllByUser(targetUser).stream().map(
UserTag::getTag).toList();

int matchingCount = 0;
for (Tag tag : loginUserTag) {
if (targetUserTag.contains(tag)) {
matchingCount++;
}
}

return (int) (((double) matchingCount / loginUserTag.size()) * 100);
}

}
24 changes: 23 additions & 1 deletion src/test/java/com/cvsgo/controller/UserControllerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@
import com.cvsgo.config.WebConfig;
import com.cvsgo.dto.product.ConvenienceStoreEventDto;
import com.cvsgo.dto.product.ProductSortBy;
import com.cvsgo.dto.product.ReadUserProductRequestDto;
import com.cvsgo.dto.product.ReadProductQueryDto;
import com.cvsgo.dto.product.ReadProductResponseDto;
import com.cvsgo.dto.product.ReadUserProductRequestDto;
import com.cvsgo.dto.user.SignUpRequestDto;
import com.cvsgo.dto.user.SignUpResponseDto;
import com.cvsgo.dto.user.UpdateUserRequestDto;
Expand Down Expand Up @@ -473,6 +473,28 @@ void respond_404_when_delete_user_follow_but_user_follow_does_not_exist() throws
.andDo(print());
}

@Test
@DisplayName("태그 매칭률을 정상적으로 조회하면 HTTP 200을 응답한다")
void respond_200_when_read_user_tag_match_percentage_successfully() throws Exception {
Integer percentage = 66;
given(userService.readUserTagMatchPercentage(any(), anyLong())).willReturn(percentage);

mockMvc.perform(get("/api/users/{userId}/tag-match-percentage", 1L)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(print())
.andDo(document(documentIdentifier,
getDocumentRequest(),
getDocumentResponse(),
pathParameters(
parameterWithName("userId").description("태그 매칭률을 조회할 회원 ID")
),
relaxedResponseFields(
fieldWithPath("data").type(JsonFieldType.NUMBER).description("태그 매칭률")
)
));
}

@Test
@DisplayName("특정 회원의 좋아요 상품 목록을 정상적으로 조회하면 HTTP 200을 응답한다")
void respond_200_when_read_liked_product_list_successfully() throws Exception {
Expand Down
72 changes: 72 additions & 0 deletions src/test/java/com/cvsgo/repository/UserTagRepositoryTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.cvsgo.repository;

import static org.assertj.core.api.Assertions.assertThat;

import com.cvsgo.config.TestConfig;
import com.cvsgo.entity.Tag;
import com.cvsgo.entity.User;
import com.cvsgo.entity.UserTag;
import java.util.List;
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.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.Import;

@Import(TestConfig.class)
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class UserTagRepositoryTest {

@Autowired
private UserTagRepository userTagRepository;

@Autowired
private UserRepository userRepository;

@Autowired
private TagRepository tagRepository;

User user;

@BeforeEach
void initData() {
tag1 = Tag.builder().name("맵찔이").group(1).build();
tag2 = Tag.builder().name("맵부심").group(1).build();
tag3 = Tag.builder().name("초코러버").group(2).build();
tag4 = Tag.builder().name("비건").group(3).build();
tag5 = Tag.builder().name("다이어터").group(4).build();
tag6 = Tag.builder().name("대식가").group(5).build();
tag7 = Tag.builder().name("소식가").group(5).build();
tagRepository.saveAll(List.of(tag1, tag2, tag3, tag4, tag5, tag6, tag7));

user1 = User.create("[email protected]", "password1!", "닉네임1", List.of(tag1, tag3, tag6));
user2 = User.create("[email protected]", "password1!", "닉네임2", List.of(tag2, tag4, tag6));
userRepository.saveAll(List.of(user1, user2));
}

@Test
@DisplayName("유저 태그를 조회한다")
void find_user_tags_by_user() {
// when
List<UserTag> userTags = userTagRepository.findAllByUser(user1);

// then
List<Tag> tags = userTags.stream().map(UserTag::getTag).toList();
assertThat(userTags).hasSize(3);
assertThat(tags).extracting("name").containsExactly("맵찔이", "초코러버", "대식가");
assertThat(tags).extracting("name").doesNotContain("맵부심", "비건", "다이어터", "소식가");
}

private Tag tag1;
private Tag tag2;
private Tag tag3;
private Tag tag4;
private Tag tag5;
private Tag tag6;
private Tag tag7;
private User user1;
private User user2;
}
45 changes: 44 additions & 1 deletion src/test/java/com/cvsgo/service/UserServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.times;

import com.cvsgo.dto.review.ReadProductReviewResponseDto;
import com.cvsgo.dto.user.SignUpRequestDto;
import com.cvsgo.dto.user.UpdateUserRequestDto;
import com.cvsgo.entity.Review;
Expand All @@ -26,6 +25,7 @@
import com.cvsgo.repository.TagRepository;
import com.cvsgo.repository.UserFollowRepository;
import com.cvsgo.repository.UserRepository;
import com.cvsgo.repository.UserTagRepository;
import jakarta.persistence.EntityManager;
import java.util.Arrays;
import java.util.List;
Expand All @@ -51,6 +51,9 @@ class UserServiceTest {
@Mock
private TagRepository tagRepository;

@Mock
private UserTagRepository userTagRepository;

@Mock
private UserFollowRepository userFollowRepository;

Expand Down Expand Up @@ -310,6 +313,30 @@ void should_throw_NotFoundException_when_delete_user_follow_but_user_follow_does
then(userFollowRepository).should(times(1)).findByUserAndFollower(any(), any());
}

@Test
@DisplayName("태그 매칭률을 조회한다")
void succeed_to_read_user_tag_match_percentage() {
given(userRepository.findById(anyLong())).willReturn(Optional.of(user2));
given(userTagRepository.findAllByUser(any())).willReturn(List.of(userTag1, userTag2));
given(userTagRepository.findAllByUser(any())).willReturn(List.of(userTag3, userTag4));

userService.readUserTagMatchPercentage(user, user2.getId());

then(userRepository).should(times(1)).findById(user2.getId());
then(userTagRepository).should(times(2)).findAllByUser(any());
}

@Test
@DisplayName("태그 매칭률 조회 시 해당하는 아이디를 가진 사용자가 없으면 NotFoundException이 발생한다")
void should_throw_NotFoundException_when_read_user_tag_match_percentage_but_user_does_not_exist() {
final Long userId = 10000L;

given(userRepository.findById(anyLong())).willReturn(Optional.empty());

assertThrows(NotFoundException.class, () -> userService.readUserTagMatchPercentage(user, userId));
then(userRepository).should(times(1)).findById(anyLong());
}

User user = User.builder()
.id(1L)
.userId("[email protected]")
Expand All @@ -336,16 +363,32 @@ void should_throw_NotFoundException_when_delete_user_follow_but_user_follow_does
.group(2)
.build();

Tag tag3 = Tag.builder()
.id(5L)
.name("다이어터")
.group(4)
.build();

UserTag userTag1 = UserTag.builder()
.user(user)
.tag(tag1)
.build();

UserTag userTag2 = UserTag.builder()
.user(user)
.tag(tag3)
.build();

UserTag userTag3 = UserTag.builder()
.user(user2)
.tag(tag2)
.build();

UserTag userTag4 = UserTag.builder()
.user(user2)
.tag(tag3)
.build();

Review review1 = Review.builder()
.user(user)
.build();
Expand Down