Skip to content

Commit

Permalink
feat: 유저 리뷰 조회 기능 (#152)
Browse files Browse the repository at this point in the history
* feat: 회원 리뷰 조회 기능 구현 (#147)

* feat: 회원 리뷰 조회 기능 구현 (#147)

* test: 회원 리뷰 조회 기능 단위 테스트 (#147)

* test: 회원 리뷰 조회 기능 단위 테스트 (service) (#147)

* test: 회원 리뷰 조회 기능 통합 테스트 (#147)
  • Loading branch information
choidongkuen authored Jan 27, 2024
1 parent 453cc51 commit d24c6a7
Show file tree
Hide file tree
Showing 10 changed files with 182 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import net.teumteum.user.domain.response.UserGetResponse;
import net.teumteum.user.domain.response.UserMeGetResponse;
import net.teumteum.user.domain.response.UserRegisterResponse;
import net.teumteum.user.domain.response.UserReviewsResponse;
import net.teumteum.user.domain.response.UsersGetByIdResponse;
import net.teumteum.user.service.UserService;
import org.springframework.http.HttpStatus;
Expand Down Expand Up @@ -115,6 +116,12 @@ public void registerReview(
userService.registerReview(meetingId, request);
}

@GetMapping("/reviews")
@ResponseStatus(HttpStatus.OK)
public List<UserReviewsResponse> getUserReviews() {
return userService.getUserReviews(getCurrentUserId());
}

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResponse handleIllegalArgumentException(IllegalArgumentException illegalArgumentException) {
Expand Down
13 changes: 8 additions & 5 deletions src/main/java/net/teumteum/user/domain/UserRepository.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
package net.teumteum.user.domain;

import java.util.List;
import java.util.Optional;
import net.teumteum.core.security.Authenticated;
import net.teumteum.user.domain.response.UserReviewsResponse;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.Optional;

public interface UserRepository extends JpaRepository<User, Long> {

@Query("select u from users u " +
"where u.oauth.authenticated = :authenticated and u.oauth.oauthId = :oAuthId")
"where u.oauth.authenticated = :authenticated and u.oauth.oauthId = :oAuthId")
Optional<User> findByAuthenticatedAndOAuthId(@Param("authenticated") Authenticated authenticated,
@Param("oAuthId") String oAuthId);

@Param("oAuthId") String oAuthId);

@Query("select new net.teumteum.user.domain.response.UserReviewsResponse(r,count(r)) "
+ "from users u join u.reviews r where u.id = :userId group by r")
List<UserReviewsResponse> countUserReviewsByUserId(@Param("userId") Long userId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package net.teumteum.user.domain.response;

import net.teumteum.user.domain.Review;

public record UserReviewsResponse(
Review review,
long count
) {

}
4 changes: 4 additions & 0 deletions src/main/java/net/teumteum/user/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import net.teumteum.user.domain.response.UserGetResponse;
import net.teumteum.user.domain.response.UserMeGetResponse;
import net.teumteum.user.domain.response.UserRegisterResponse;
import net.teumteum.user.domain.response.UserReviewsResponse;
import net.teumteum.user.domain.response.UsersGetByIdResponse;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand Down Expand Up @@ -112,6 +113,9 @@ public void registerReview(Long meetingId, ReviewRegisterRequest request) {
});
}

public List<UserReviewsResponse> getUserReviews(Long userId) {
return userRepository.countUserReviewsByUserId(userId);
}

public FriendsResponse findFriendsByUserId(Long userId) {
var user = getUser(userId);
Expand Down
9 changes: 8 additions & 1 deletion src/test/java/net/teumteum/integration/Api.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import net.teumteum.meeting.config.PageableHandlerMethodArgumentResolver;
import net.teumteum.meeting.domain.Topic;
import net.teumteum.teum_teum.domain.request.UserLocationRequest;
import net.teumteum.user.domain.request.ReviewRegisterRequest;
import net.teumteum.user.domain.request.UserRegisterRequest;
import net.teumteum.user.domain.request.UserUpdateRequest;
import net.teumteum.user.domain.request.UserWithdrawRequest;
Expand Down Expand Up @@ -181,4 +180,12 @@ ResponseSpec deleteMeeting(String accessToken, Long meetingId) {
.header(HttpHeaders.AUTHORIZATION, accessToken)
.exchange();
}

ResponseSpec getUserReviews(String accessToken) {
return webTestClient
.get()
.uri("/users/reviews")
.header(HttpHeaders.AUTHORIZATION, accessToken)
.exchange();
}
}
28 changes: 26 additions & 2 deletions src/test/java/net/teumteum/integration/UserIntegrationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,16 @@
import net.teumteum.core.error.ErrorResponse;
import net.teumteum.user.domain.User;
import net.teumteum.user.domain.UserFixture;
import net.teumteum.user.domain.request.ReviewRegisterRequest;
import net.teumteum.user.domain.response.FriendsResponse;
import net.teumteum.user.domain.response.UserGetResponse;
import net.teumteum.user.domain.response.UserMeGetResponse;
import net.teumteum.user.domain.response.UserRegisterResponse;
import net.teumteum.user.domain.response.UserReviewsResponse;
import net.teumteum.user.domain.response.UsersGetByIdResponse;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.test.web.reactive.server.WebTestClient.ResponseSpec;

@DisplayName("유저 통합테스트의")
class UserIntegrationTest extends IntegrationTest {
Expand Down Expand Up @@ -349,4 +348,29 @@ void Logout_user() {
.doesNotThrowAnyException();
}
}

@Nested
@DisplayName("회원 리뷰 조회 API는")
class Get_user_review_api {

@Test
@DisplayName("userId 유저의 리뷰 정보를 가져온다.")
void Get_user_review() {
// given
var existUser = repository.saveAndGetUser();

securityContextSetting.set(existUser.getId());

// when
var expected = api.getUserReviews(VALID_TOKEN);

// then
Assertions.assertThat(expected.expectStatus().isOk()
.expectBodyList(UserReviewsResponse.class)
.returnResult()
.getResponseBody())
.usingRecursiveComparison()
.isNotNull();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,18 @@

import static net.teumteum.unit.auth.common.SecurityValue.VALID_ACCESS_TOKEN;
import static net.teumteum.unit.auth.common.SecurityValue.VALID_REFRESH_TOKEN;
import static net.teumteum.user.domain.Review.별로에요;
import static net.teumteum.user.domain.Review.최고에요;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.BDDMockito.given;
import static org.springframework.http.HttpHeaders.AUTHORIZATION;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
Expand All @@ -27,6 +34,7 @@
import net.teumteum.user.domain.request.UserRegisterRequest;
import net.teumteum.user.domain.request.UserWithdrawRequest;
import net.teumteum.user.domain.response.UserRegisterResponse;
import net.teumteum.user.domain.response.UserReviewsResponse;
import net.teumteum.user.service.UserService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
Expand Down Expand Up @@ -156,4 +164,34 @@ void Register_user_review_with_200_ok() throws Exception {
.andExpect(status().isOk());
}
}


@Nested
@DisplayName("회원 리뷰 조회 API는")
class Get_user_reviews_api_unit {

@Test
@DisplayName("로그인한 회원 id 에 해당하는 회원 리뷰와 200 OK을 반환한다.")
void Get_user_reviews_with_200_ok() throws Exception {
// given
var userId = 1L;

given(securityService.getCurrentUserId()).willReturn(userId);

given(userService.getUserReviews(anyLong()))
.willReturn(List.of(new UserReviewsResponse(별로에요, 2L),
new UserReviewsResponse(최고에요, 3L)));

// when & then
mockMvc.perform(get("/users/reviews")
.with(csrf())
.header(AUTHORIZATION, VALID_ACCESS_TOKEN))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$", notNullValue()))
.andExpect(jsonPath("$", hasSize(2)))
.andExpect(jsonPath("$[0].count", is(2)))
.andExpect(jsonPath("$[0].review").value("별로에요"));
}
}
}
40 changes: 36 additions & 4 deletions src/test/java/net/teumteum/unit/user/service/UserServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import static net.teumteum.unit.auth.common.SecurityValue.VALID_ACCESS_TOKEN;
import static net.teumteum.unit.auth.common.SecurityValue.VALID_REFRESH_TOKEN;
import static net.teumteum.user.domain.Review.별로에요;
import static net.teumteum.user.domain.Review.최고에요;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
Expand All @@ -27,6 +29,7 @@
import net.teumteum.user.domain.request.UserRegisterRequest;
import net.teumteum.user.domain.request.UserWithdrawRequest;
import net.teumteum.user.domain.response.UserRegisterResponse;
import net.teumteum.user.domain.response.UserReviewsResponse;
import net.teumteum.user.service.UserService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
Expand Down Expand Up @@ -157,13 +160,13 @@ void Register_user_review_with_200_ok() {
userService.registerReview(meetingId, reviewRegisterRequest);

// then
verify(meetingConnector,times(1)).existById(anyLong());
verify(userRepository,times(3)).findById(anyLong());
verify(meetingConnector, times(1)).existById(anyLong());
verify(userRepository, times(3)).findById(anyLong());
}

@Test
@DisplayName("meeting id 에 해당하는 meeting 이 존재하지 않는 경우, 400 Bad Request 와 함께 리뷰 등록을 실패한다.")
void Return_400_bad_request_if_meeting_is_not_exist(){
void Return_400_bad_request_if_meeting_is_not_exist() {
// given
ReviewRegisterRequest reviewRegisterRequest = RequestFixture.reviewRegisterRequest();

Expand All @@ -172,9 +175,38 @@ void Return_400_bad_request_if_meeting_is_not_exist(){
given(meetingConnector.existById(anyLong()))
.willReturn(false);
// when & then
assertThatThrownBy( () -> userService.registerReview(meetingId,reviewRegisterRequest))
assertThatThrownBy(() -> userService.registerReview(meetingId, reviewRegisterRequest))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("meetingId에 해당하는 meeting을 찾을 수 없습니다. \"" + meetingId + "\"");
}
}

@Nested
@DisplayName("회원 리뷰 조회 API는")
class Get_user_reviews_api_unit {

@Test
@DisplayName("로그인한 회원의 리뷰 리스트로 200 OK 응답한다.")
void Return_user_reviews_with_200_ok() {
// given
var userId = 1L;

var response = List.of(new UserReviewsResponse(최고에요, 2L)
, new UserReviewsResponse(별로에요, 3L));

given(userRepository.countUserReviewsByUserId(anyLong())).willReturn(response);

// when
var result = userService.getUserReviews(userId);

// then
assertThat(result).hasSize(2);
assertThat(result.get(0).review()).isEqualTo(최고에요);
assertThat(result.get(0).count()).isEqualTo(2L);
assertThat(result.get(1).review()).isEqualTo(별로에요);
assertThat(result.get(1).count()).isEqualTo(3L);

verify(userRepository, times(1)).countUserReviewsByUserId(anyLong());
}
}
}
6 changes: 4 additions & 2 deletions src/test/java/net/teumteum/user/domain/UserFixture.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package net.teumteum.user.domain;

import static net.teumteum.core.security.Authenticated.네이버;
import static net.teumteum.user.domain.Review.*;
import static net.teumteum.user.domain.Review.별로에요;
import static net.teumteum.user.domain.Review.좋아요;
import static net.teumteum.user.domain.Review.최고에요;

import java.util.List;
import java.util.Set;
Expand Down Expand Up @@ -87,7 +89,7 @@ public static class UserBuilder {
@Builder.Default
private Terms terms = new Terms(true, true);
@Builder.Default
private List<Review> reviews = List.of(최고에요);
private List<Review> reviews = List.of(최고에요, 최고에요, 최고에요, 별로에요, 좋아요, 좋아요);
}

}
50 changes: 41 additions & 9 deletions src/test/java/net/teumteum/user/domain/UserRepositoryTest.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package net.teumteum.user.domain;

import static net.teumteum.user.domain.Review.별로에요;
import static net.teumteum.user.domain.Review.좋아요;
import static net.teumteum.user.domain.Review.최고에요;

import jakarta.persistence.EntityManager;
import java.util.Optional;
import net.teumteum.core.config.AppConfig;
import net.teumteum.user.domain.response.UserReviewsResponse;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
Expand All @@ -10,8 +16,6 @@
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.Import;

import java.util.Optional;

@DataJpaTest
@Import(AppConfig.class)
@DisplayName("UserRepository 클래스의")
Expand Down Expand Up @@ -49,22 +53,50 @@ class FindById_method {
@DisplayName("저장된 유저의 id로 조회하면, 유저를 반환한다.")
void Find_success_if_exists_user_id_input() {
// given
var id = 1L;
var existsUser = UserFixture.getUserWithId(id);
var existsUser = UserFixture.getNullIdUser();

userRepository.saveAndFlush(existsUser);
entityManager.clear();

// when
var result = userRepository.findById(id);
var result = userRepository.findById(existsUser.getId());

// then
Assertions.assertThat(result)
.isPresent()
.usingRecursiveComparison()
.ignoringFields("value.createdAt", "value.updatedAt")
.isEqualTo(Optional.of(existsUser));
.usingRecursiveComparison()
.ignoringFields("value.createdAt", "value.updatedAt")
.isEqualTo(Optional.ofNullable(existsUser));
}
}


@Nested
@DisplayName("countUserReviewsByUserId 메소드는")
class CountUserReviewsByUserId_method {

@Test
@DisplayName("저장된 유저의 id 을 이용해서 유저의 리뷰 갯수를 조회하면, UserReviewResponse 을 반환한다.")
void Count_user_reviews_by_user_id() {
// given
var id = 1L;
var existUser = UserFixture.getUserWithId(id);

userRepository.saveAndFlush(existUser);
entityManager.clear();

// when
var result = userRepository.countUserReviewsByUserId(id);

// then
Assertions.assertThat(result)
.isNotEmpty()
.hasSize(3)
.extracting(UserReviewsResponse::review, UserReviewsResponse::count)
.contains(
Assertions.tuple(최고에요, 3L),
Assertions.tuple(별로에요, 1L),
Assertions.tuple(좋아요, 2L));

}
}
}

0 comments on commit d24c6a7

Please sign in to comment.