diff --git a/.gitignore b/.gitignore index bef95b8d..216ced7d 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,6 @@ bin/ ### Mac OS ### .DS_Store + +### .env ### +.env diff --git a/Docker-compose.yml b/Docker-compose.yml new file mode 100644 index 00000000..edafb9ae --- /dev/null +++ b/Docker-compose.yml @@ -0,0 +1,56 @@ +services: + db: + image: "mysql:8.3.0" + container_name: load_test_mysql + restart: always + environment: + MYSQL_ROOT_PASSWORD: root#1234 + MYSQL_DATABASE: load_test_db + TZ: UTC + + ports: + - "3000:3306" + deploy: + resources: + limits: + cpus: "0.5" + memory: "512MB" + + redis: + image: "docker.io/bitnami/redis:7.2" + container_name: load_test_redis + restart: always + environment: + - ALLOW_EMPTY_PASSWORD=yes + - REDIS_AOF_ENABLED=yes + - REDIS_RDB_ENABLED=no + ports: + - "6299:6379" + deploy: + resources: + limits: + cpus: "0.5" + memory: "512MB" + + teumteum-server: + build: + context: . + dockerfile: Dockerfile + restart: always + environment: + DB_URL: jdbc:mysql://db:3306/load_test_db + DB_USERNAME: root + DB_PASSWORD: root#1234 + REDIS_HOST: redis + REDIS_PORT: 6379 + JWT_SECERT_KEY: ${JWT_ACCESS_KEY} + + depends_on: + - db + - redis + ports: + - "8080:8080" + +networks: + teumteum_local: + driver: bridge diff --git a/src/gatling/java/protocol/Protocol.java b/src/gatling/java/protocol/Protocol.java index b131c75a..6f8fe8da 100644 --- a/src/gatling/java/protocol/Protocol.java +++ b/src/gatling/java/protocol/Protocol.java @@ -7,7 +7,7 @@ public class Protocol { private static final String USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0"; - public static final HttpProtocolBuilder httpProtocol = HttpDsl.http.baseUrl("https://api.teum.org") + public static final HttpProtocolBuilder httpProtocol = HttpDsl.http.baseUrl("http://localhost:8080") .header("Content-Type", "application/json") .userAgentHeader(USER_AGENT); diff --git a/src/gatling/java/simulation/SimulationSample.java b/src/gatling/java/simulation/SimulationSample.java index c5f90cfd..1c137d2d 100644 --- a/src/gatling/java/simulation/SimulationSample.java +++ b/src/gatling/java/simulation/SimulationSample.java @@ -16,8 +16,7 @@ public class SimulationSample extends Simulation { private final ScenarioBuilder scn = scenario(this.getClass().getSimpleName()) .exec(http("get user") .get("/users/1") - .check(status().is(200)) - ); + .check(status().is(200))); { setUp( diff --git a/src/gatling/java/simulation/TeumTeumApiSimulation.java b/src/gatling/java/simulation/TeumTeumApiSimulation.java new file mode 100644 index 00000000..b06f56bd --- /dev/null +++ b/src/gatling/java/simulation/TeumTeumApiSimulation.java @@ -0,0 +1,66 @@ +package simulation; + +import static io.gatling.javaapi.core.CoreDsl.StringBody; +import static io.gatling.javaapi.core.CoreDsl.constantUsersPerSec; +import static io.gatling.javaapi.core.CoreDsl.jsonPath; +import static io.gatling.javaapi.core.CoreDsl.rampUsersPerSec; +import static io.gatling.javaapi.core.CoreDsl.scenario; +import static io.gatling.javaapi.http.HttpDsl.http; +import static io.gatling.javaapi.http.HttpDsl.status; +import static protocol.Protocol.httpProtocol; + +import io.gatling.javaapi.core.ScenarioBuilder; +import io.gatling.javaapi.core.Simulation; +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import net.datafaker.Faker; + +public class TeumTeumApiSimulation extends Simulation { + + private static final Faker faker = new Faker(); + private final ScenarioBuilder teumteumScn = scenario("TeumTeum 찬해지기 API 부하 테스트를 진행한다.") + .exec(session -> + session + .set("id", java.util.UUID.randomUUID().toString()) + .set("name", faker.name().fullName())) + + .exec(http("User 카드 등록 API 요청") + .post("/users") + .body(StringBody( + "{" + + "\"id\": \"${id}\", " + + "\"terms\": {\"service\": true, \"privatePolicy\": true}, " + + "\"name\": \"${name}\", " + + "\"birth\": \"20000402\", " + + "\"characterId\": 2, " + + "\"authenticated\": \"네이버\", " + + "\"activityArea\": \"경기 시흥\", " + + "\"mbti\": \"ENFP\", " + + "\"status\": \"직장인\", " + + "\"job\": {\"name\" : \"카카오 뱅크\", \"class\" : \"개발\", \"detailClass\" : \"BE 개발자\"}, " + + "\"interests\": [\"네트워킹\", \"IT\", \"모여서 각자 일하기\"], " + + "\"goal\": \"회사에서 좋은 사람들과 멋진 개발하기\"" + + "}" + )) + .check(status().is(201)) + .check(jsonPath("$.id").saveAs("userId")) + .check(jsonPath("$.accessToken").saveAs("accessToken")) + .check(jsonPath("$.refreshToken").saveAs("refreshToken"))) + + .exec(http("TeumTeum 친해지기 API 요청") + .post("/teum-teum/around") + .header("Authorization", "Bearer ${accessToken}") + .body(StringBody("{\"id\": ${userId}, \"latitude\": 37.5665, \"longitude\": 126.9780," + + " \"name\": \"test_name\", \"jobDetailClass\": \"test_job\", \"characterId\": 1}")) + .check(status().is(200)) + ); + + { + setUp( + teumteumScn.injectOpen( + constantUsersPerSec(10).during(Duration.of(30, ChronoUnit.SECONDS)), + rampUsersPerSec(10).to(50).during(Duration.of(30, ChronoUnit.SECONDS)) + ).protocols(httpProtocol) + ); + } +} diff --git a/src/gatling/java/simulation/UserApiSimulation.java b/src/gatling/java/simulation/UserApiSimulation.java new file mode 100644 index 00000000..9f7aceee --- /dev/null +++ b/src/gatling/java/simulation/UserApiSimulation.java @@ -0,0 +1,54 @@ +package simulation; + +import static io.gatling.javaapi.core.CoreDsl.StringBody; +import static io.gatling.javaapi.core.CoreDsl.atOnceUsers; +import static io.gatling.javaapi.core.CoreDsl.jsonPath; +import static io.gatling.javaapi.core.CoreDsl.scenario; +import static io.gatling.javaapi.http.HttpDsl.http; +import static io.gatling.javaapi.http.HttpDsl.status; +import static protocol.Protocol.httpProtocol; + +import io.gatling.javaapi.core.ScenarioBuilder; +import io.gatling.javaapi.core.Simulation; + +public class UserApiSimulation extends Simulation { + + private final ScenarioBuilder UserScn = scenario("User API 부하 테스트를 진행한다.") + .exec(http("User 카드 등록 API 요청") + .post("/users") + .body(StringBody( + "{\"id\": \"test_id\", " + + "\"terms\": {\"service\": true, \"privatePolicy\": true}, " + + "\"name\": \"홍길동\", " + + "\"birth\": \"1990-01-01\", " + + "\"characterId\": 1, " + + "\"authenticated\": \"SNS\", " + + "\"activityArea\": \"서울\", " + + "\"mbti\": \"INTJ\", " + + "\"status\": \"ACTIVE\", " + + "\"job\": {\"name\": \"개발자\", \"class\": \"IT\", \"detailClass\": \"백엔드\"}, " + + "\"interests\": [\"코딩\", \"독서\", \"운동\"], " + + "\"goal\": \"성장하기 위해 노력하는 개발자가 되기\"}" + )) + .check(status().is(201)) + .check(jsonPath("$.id").saveAs("userId")) + .check(jsonPath("$.accessToken").saveAs("accessToken")) + .check(jsonPath("$.refreshToken").saveAs("refreshToken")) + + ).exec(http("User 정보 조회 API 요청") + .get("/users/${userId}") + .header("Authorization", "Bearer ${accessToken}") + .check(status().is(200)) + + ).exec(http("User 리뷰 조회 API 요청") + .get("/users/${userId}") + .header("Authorization", "Bearer ${accessToken}") + .check(status().is(200))); + + + { + setUp( + UserScn.injectOpen( + atOnceUsers(10)).protocols(httpProtocol)); + } +} diff --git a/src/main/java/net/teumteum/core/security/SecurityConfig.java b/src/main/java/net/teumteum/core/security/SecurityConfig.java index d9032047..066c5dae 100644 --- a/src/main/java/net/teumteum/core/security/SecurityConfig.java +++ b/src/main/java/net/teumteum/core/security/SecurityConfig.java @@ -66,7 +66,7 @@ CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.addAllowedOrigin("*"); config.addAllowedHeader("*"); - config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")); + config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD", "TRACE")); config.addExposedHeader("Authorization"); config.addExposedHeader("Authorization-refresh"); config.setAllowCredentials(true); diff --git a/src/main/java/net/teumteum/user/domain/request/ReviewRegisterRequest.java b/src/main/java/net/teumteum/user/domain/request/ReviewRegisterRequest.java index b5cc4bbb..e94d89c0 100644 --- a/src/main/java/net/teumteum/user/domain/request/ReviewRegisterRequest.java +++ b/src/main/java/net/teumteum/user/domain/request/ReviewRegisterRequest.java @@ -8,7 +8,7 @@ public record ReviewRegisterRequest( @Valid - @Size(min = 2, max = 5) + @Size(min = 1, max = 5) List reviews ) { diff --git a/src/main/java/net/teumteum/user/service/UserService.java b/src/main/java/net/teumteum/user/service/UserService.java index 76de43d1..557daad3 100644 --- a/src/main/java/net/teumteum/user/service/UserService.java +++ b/src/main/java/net/teumteum/user/service/UserService.java @@ -1,11 +1,13 @@ package net.teumteum.user.service; +import java.time.LocalDateTime; import java.util.List; import lombok.RequiredArgsConstructor; import net.teumteum.core.security.Authenticated; import net.teumteum.core.security.service.JwtService; import net.teumteum.core.security.service.RedisService; import net.teumteum.core.security.service.SecurityService; +import net.teumteum.meeting.domain.Meeting; import net.teumteum.meeting.domain.MeetingConnector; import net.teumteum.user.domain.BalanceGameType; import net.teumteum.user.domain.InterestQuestion; @@ -109,7 +111,10 @@ public void logout(Long userId) { @Transactional public void registerReview(Long meetingId, Long currentUserId, ReviewRegisterRequest request) { - checkMeetingExistence(meetingId); + var meeting = getMeeting(meetingId); + + checkMeetingIsClosed(meeting); + checkUserParticipationInMeeting(meeting, currentUserId); checkUserNotRegisterSelfReview(request, currentUserId); request.reviews() @@ -157,12 +162,9 @@ private void checkUserExistence(Authenticated authenticated, String oauthId) { ); } - private void checkMeetingExistence(Long meetingId) { - Assert.isTrue(meetingConnector.existById(meetingId), - () -> { - throw new IllegalArgumentException("meetingId에 해당하는 meeting을 찾을 수 없습니다. \"" + meetingId + "\""); - } - ); + private Meeting getMeeting(Long meetingId) { + return meetingConnector.findById(meetingId) + .orElseThrow(() -> new IllegalArgumentException("meetingId에 해당하는 모임을 찾을 수 없습니다. \"" + meetingId + "\"")); } private void checkUserNotRegisterSelfReview(ReviewRegisterRequest request, Long currentUserId) { @@ -172,4 +174,16 @@ private void checkUserNotRegisterSelfReview(ReviewRegisterRequest request, Long } ); } + + private void checkUserParticipationInMeeting(Meeting meeting, Long userId) { + if (!meeting.getParticipantUserIds().contains(userId)) { + throw new IllegalArgumentException("모임에 참여하지 않은 회원입니다."); + } + } + + private void checkMeetingIsClosed(Meeting meeting) { + if (!LocalDateTime.now().isAfter(meeting.getPromiseDateTime())) { + throw new IllegalArgumentException("해당 모임은 아직 종료되지 않았습니다."); + } + } } diff --git a/src/test/java/net/teumteum/integration/Repository.java b/src/test/java/net/teumteum/integration/Repository.java index 894c0369..fd0f9002 100644 --- a/src/test/java/net/teumteum/integration/Repository.java +++ b/src/test/java/net/teumteum/integration/Repository.java @@ -143,6 +143,11 @@ List saveAndGetCloseMeetingsByParticipantUserId(int size, Long particip return meetingRepository.saveAllAndFlush(meetings); } + Meeting saveAndGetCloseMeetingByParticipantUserIds(List participantUserIds) { + var meeting = MeetingFixture.getCloseMeetingWithParticipantIds(participantUserIds); + return meetingRepository.save(meeting); + } + List saveAndGetOpenMeetings(int size) { var meetings = Stream.generate(MeetingFixture::getOpenMeeting) .limit(size) diff --git a/src/test/java/net/teumteum/integration/UserIntegrationTest.java b/src/test/java/net/teumteum/integration/UserIntegrationTest.java index 8b1503a1..40ddd08a 100644 --- a/src/test/java/net/teumteum/integration/UserIntegrationTest.java +++ b/src/test/java/net/teumteum/integration/UserIntegrationTest.java @@ -4,10 +4,8 @@ import java.util.List; import net.teumteum.core.error.ErrorResponse; -import net.teumteum.meeting.domain.Meeting; 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; @@ -15,7 +13,6 @@ 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.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -341,48 +338,101 @@ void Get_user_reviews() { @DisplayName("회원 리뷰 등록 API는") class Register_user_review_api { - User existUser; + @Test + @DisplayName("정상적인 요청이 오는 경우, 해당 회원의 리뷰 등록과 함께 200 OK 을 반환한다.") + void Return_200_OK_and_register_review_if_request_is_valid() { + // given + var user = repository.saveAndGetUser(); + var participant1 = repository.saveAndGetUser(); + var participant2 = repository.saveAndGetUser(); - List users; + var closedMeeting = repository.saveAndGetCloseMeetingByParticipantUserIds( + List.of(user.getId(), participant1.getId(), participant2.getId())); - ReviewRegisterRequest request; + var request = RequestFixture.reviewRegisterRequest(List.of(participant1, participant2)); - Meeting meeting; + securityContextSetting.set(user.getId()); - @BeforeEach - void setUp() { - existUser = repository.saveAndGetUser(); - users = repository.saveAndGetUsers(3); - request = RequestFixture.reviewRegisterRequest(users); - meeting = repository.saveAndGetOpenMeetings(1).get(0); + // when + var expected = api.registerUserReview(VALID_TOKEN, closedMeeting.getId(), request); + + // then + Assertions.assertThat(expected.expectStatus().isOk()); } @Test - @DisplayName("회원 리뷰 등록 요청이 들어오면 리뷰를 등록하고, 200 OK 을 반환한다.") - void Return_200_OK_with_success_register_user_review() { + @DisplayName("meeting id 에 해당하는 meeting 이 아직 종료되지 않았다면, 400 Bad Request 와 함께 리뷰 등록을 실패한다.") + void Return_400_bad_request_if_meeting_is_not_closed() { // given - securityContextSetting.set(existUser.getId()); + var user = repository.saveAndGetUser(); + var participant = repository.saveAndGetUser(); + + var openMeeting = repository.saveAndGetOpenMeeting(); + var request = RequestFixture.reviewRegisterRequest(List.of(participant)); + + securityContextSetting.set(user.getId()); // when - var expected = api.registerUserReview(VALID_TOKEN, meeting.getId(), request); + var expected = api.registerUserReview(VALID_TOKEN, openMeeting.getId(), request); // then - Assertions.assertThat(expected.expectStatus().isOk()); + Assertions.assertThat(expected.expectStatus().isBadRequest() + .expectBody(ErrorResponse.class) + .returnResult().getResponseBody()) + .extracting(ErrorResponse::getMessage) + .isEqualTo("해당 모임은 아직 종료되지 않았습니다."); } @Test @DisplayName("현재 로그인한 회원의 id 가 리뷰 등록 요청에 포함된다면, 회원 리뷰 등록을 실패하고 400 bad request 을 반환한다.") void Return_400_bad_request_if_current_user_id_in_request() { // given - securityContextSetting.set(users.get(0).getId()); + var user = repository.saveAndGetUser(); + var participant = repository.saveAndGetUser(); + + var closedMeeting = repository.saveAndGetCloseMeetingByParticipantUserIds( + List.of(user.getId(), participant.getId())); + + var request = RequestFixture.reviewRegisterRequest(List.of(user, participant)); + + securityContextSetting.set(user.getId()); + + // when + var expected = api.registerUserReview(VALID_TOKEN, closedMeeting.getId(), request); + + // then + Assertions.assertThat(expected.expectStatus().isBadRequest() + .expectBody(ErrorResponse.class) + .returnResult().getResponseBody()) + .extracting(ErrorResponse::getMessage) + .isEqualTo("나의 리뷰에 대한 리뷰를 작성할 수 없습니다."); + } + + @Test + @DisplayName("현재 로그인한 회원의 id 가 모임 참여자에 포함되지 않는다면, 회원 리뷰 등록을 실패하고 400 bad request 을 반환한다.") + void Return_400_bad_request_if_meeting_not_contain_current_user_id_() { + // given + var user = repository.saveAndGetUser(); + var participant1 = repository.saveAndGetUser(); + var participant2 = repository.saveAndGetUser(); + + var closedMeeting = repository.saveAndGetCloseMeetingByParticipantUserIds( + List.of(participant1.getId(), participant2.getId())); + + var request = RequestFixture.reviewRegisterRequest(List.of(participant1, participant2)); + + securityContextSetting.set(user.getId()); // when - var expected = api.registerUserReview(VALID_TOKEN, meeting.getId(), request); + var expected = api.registerUserReview(VALID_TOKEN, closedMeeting.getId(), request); // then Assertions.assertThat(expected.expectStatus().isBadRequest() - .expectBody(ErrorResponse.class) - .returnResult().getResponseBody()); + .expectBody(ErrorResponse.class) + .returnResult().getResponseBody()) + .extracting(ErrorResponse::getMessage) + .isEqualTo("모임에 참여하지 않은 회원입니다."); } } } + diff --git a/src/test/java/net/teumteum/meeting/domain/MeetingFixture.java b/src/test/java/net/teumteum/meeting/domain/MeetingFixture.java index 09b850eb..98442a78 100644 --- a/src/test/java/net/teumteum/meeting/domain/MeetingFixture.java +++ b/src/test/java/net/teumteum/meeting/domain/MeetingFixture.java @@ -26,6 +26,14 @@ public static Meeting getOpenMeeting() { ); } + public static Meeting getOpenMeetingWithId(Long meetingId) { + return newMeetingByBuilder(MeetingBuilder.builder() + .id(meetingId) + .promiseDateTime(LocalDateTime.of(4000, 1, 1, 0, 0)) + .build()); + + } + public static Meeting getCloseMeeting() { return newMeetingByBuilder(MeetingBuilder.builder() .promiseDateTime(LocalDateTime.of(2000, 1, 1, 0, 0)) @@ -33,6 +41,29 @@ public static Meeting getCloseMeeting() { ); } + public static Meeting getCloseMeetingWithId(Long meetingId) { + return newMeetingByBuilder(MeetingBuilder.builder() + .id(meetingId) + .promiseDateTime(LocalDateTime.of(2000, 1, 1, 0, 0)) + .build()); + } + + public static Meeting getCloseMeetingWithIdAndParticipantIds(Long meetingId, List participantIds) { + return newMeetingByBuilder(MeetingBuilder.builder() + .id(meetingId) + .participantUserIds(new HashSet<>(participantIds)) + .promiseDateTime(LocalDateTime.of(2000, 1, 1, 0, 0)) + .build() + ); + } + + public static Meeting getCloseMeetingWithParticipantIds(List participantIds) { + return newMeetingByBuilder(MeetingBuilder.builder() + .participantUserIds(new HashSet<>(participantIds)) + .promiseDateTime(LocalDateTime.of(2000, 1, 1, 0, 0)) + .build()); + } + public static Meeting getOpenFullMeeting() { return newMeetingByBuilder(MeetingBuilder.builder() .promiseDateTime(LocalDateTime.of(4000, 1, 1, 0, 0)) diff --git a/src/test/java/net/teumteum/unit/user/controller/UserControllerTest.java b/src/test/java/net/teumteum/unit/user/controller/UserControllerTest.java index 2b1d8771..281e8be2 100644 --- a/src/test/java/net/teumteum/unit/user/controller/UserControllerTest.java +++ b/src/test/java/net/teumteum/unit/user/controller/UserControllerTest.java @@ -89,9 +89,9 @@ class Register_user_card_api_unit { @DisplayName("유효한 사용자의 등록 요청값이 주어지면, 201 Created 상태값을 반환한다.") void Register_user_card_with_201_created() throws Exception { // given - UserRegisterRequest request = RequestFixture.userRegisterRequest(user); + var request = RequestFixture.userRegisterRequest(user); - UserRegisterResponse response = new UserRegisterResponse(1L, VALID_ACCESS_TOKEN, VALID_REFRESH_TOKEN); + var response = new UserRegisterResponse(1L, VALID_ACCESS_TOKEN, VALID_REFRESH_TOKEN); given(userService.register(any(UserRegisterRequest.class))).willReturn(response); @@ -112,7 +112,7 @@ void Register_user_card_with_201_created() throws Exception { @DisplayName("이미 카드 등록한 사용자의 등록 요청값이 주어지면, 400 Bad Request을 반환한다.") void Return_400_bad_request_if_user_already_exist() throws Exception { // given - UserRegisterRequest request = RequestFixture.userRegisterRequest(user); + var request = RequestFixture.userRegisterRequest(user); given(userService.register(any(UserRegisterRequest.class))) .willThrow(new IllegalArgumentException("일치하는 user 가 이미 존재합니다.")); @@ -132,7 +132,7 @@ void Return_400_bad_request_if_user_already_exist() throws Exception { @DisplayName("유효하지 않은 사용자의 등록 요청값이 주어지면, 400 Bad Request 상태값을 반환한다.") void Register_user_card_with_400_bad_request() throws Exception { // given - UserRegisterRequest request = RequestFixture.userRegisterRequestWithNoValid(user); + var request = RequestFixture.userRegisterRequestWithNoValid(user); // when // then mockMvc.perform(post("/users") @@ -154,7 +154,7 @@ class Withdraw_user_api_unit { @DisplayName("회원 탈퇴 사유와 회원 탈퇴 요청이 들어오면, 탈퇴를 진행하고 200 OK을 반환한다.") void Withdraw_user_with_200_ok() throws Exception { // given - UserWithdrawRequest request + var request = RequestFixture.userWithdrawRequest(List.of("쓰지 않는 앱이에요", "오류가 생겨서 쓸 수 없어요")); // when & then @@ -171,7 +171,7 @@ void Withdraw_user_with_200_ok() throws Exception { @DisplayName("회원 탈퇴 하고자 하는 회원이 존재하지 않으면, 400 Bad Request을 반환한다.") void Return_400_bad_request_if_user_is_not_exist() throws Exception { // given - UserWithdrawRequest request + var request = RequestFixture.userWithdrawRequest(List.of("쓰지 않는 앱이에요", "오류가 생겨서 쓸 수 없어요")); doThrow(new IllegalArgumentException("일치하는 user가 이미 존재합니다.")).when(userService).withdraw(any( @@ -196,7 +196,7 @@ class Register_user_review_api_unit { @DisplayName("회원 id 와 리뷰 정보 요청이 들어오면, 회원 리뷰를 등록하고 200 OK을 반환한다.") void Register_user_review_with_200_ok() throws Exception { // given - ReviewRegisterRequest reviewRegisterRequest = RequestFixture.reviewRegisterRequest(); + var reviewRegisterRequest = RequestFixture.reviewRegisterRequest(); // when & then mockMvc.perform(post("/users/reviews") @@ -211,11 +211,11 @@ void Register_user_review_with_200_ok() throws Exception { @Test @DisplayName("현재 로그인한 회원의 id 가 리뷰 등록 요청에 포함된다면, 회원 리뷰 등록을 실패하고 400 bad request을 반환한다.") - void Register_reviews_with_400_bad_request() throws Exception { + void Return_400_bad_request_if_request_contains_current_user_id() throws Exception { // given - ReviewRegisterRequest reviewRegisterRequest = RequestFixture.reviewRegisterRequest(); + var reviewRegisterRequest = RequestFixture.reviewRegisterRequest(); - String errorMessage = "나의 리뷰에 대한 리뷰를 작성할 수 없습니다."; + var errorMessage = "나의 리뷰에 대한 리뷰를 작성할 수 없습니다."; doThrow(new IllegalArgumentException(errorMessage)) .when(userService) diff --git a/src/test/java/net/teumteum/unit/user/service/UserServiceTest.java b/src/test/java/net/teumteum/unit/user/service/UserServiceTest.java index 1c5630aa..f3afd61f 100644 --- a/src/test/java/net/teumteum/unit/user/service/UserServiceTest.java +++ b/src/test/java/net/teumteum/unit/user/service/UserServiceTest.java @@ -22,6 +22,7 @@ import net.teumteum.core.security.service.RedisService; import net.teumteum.integration.RequestFixture; import net.teumteum.meeting.domain.MeetingConnector; +import net.teumteum.meeting.domain.MeetingFixture; import net.teumteum.user.domain.User; import net.teumteum.user.domain.UserFixture; import net.teumteum.user.domain.UserRepository; @@ -164,60 +165,56 @@ void Register_user_review_with_200_ok() { // given ReviewRegisterRequest reviewRegisterRequest = RequestFixture.reviewRegisterRequest(); - Long meetingId = 1L; + var meeting = MeetingFixture.getCloseMeetingWithIdAndParticipantIds(1L, List.of(1L, 2L, 10L)); + var userId = 10L; - Long userId = 10L; - - Long currentUserId = 20L; - - given(meetingConnector.existById(anyLong())) - .willReturn(true); + given(meetingConnector.findById(anyLong())) + .willReturn(Optional.of(meeting)); given(userRepository.findById(anyLong())) - .willReturn(Optional.of(UserFixture.getUserWithId(userId++))); + .willReturn(Optional.of(UserFixture.getUserWithId(userId))); // when - userService.registerReview(meetingId, currentUserId, reviewRegisterRequest); + userService.registerReview(meeting.getId(), userId, reviewRegisterRequest); // then - verify(meetingConnector, times(1)).existById(anyLong()); + verify(meetingConnector, times(1)).findById(anyLong()); verify(userRepository, times(3)).findById(anyLong()); } @Test - @DisplayName("회원 id 가 리뷰 정보 요청에 포함되면, 400 Bad Request 와 함께 리뷰 등록을 실패한다.") - void Return_400_bad_request_if_current_user_id_in_request() { + @DisplayName("meeting id 에 해당하는 meeting 이 존재하지 않는 경우, 400 Bad Request 와 함께 리뷰 등록을 실패한다.") + void Return_400_bad_request_if_meeting_is_not_exist() { // given - ReviewRegisterRequest reviewRegisterRequest = RequestFixture.reviewRegisterRequest(); - - Long meetingId = 1L; + var reviewRegisterRequest = RequestFixture.reviewRegisterRequest(); - Long currentUserId = reviewRegisterRequest.reviews().get(0).id(); - - given(meetingConnector.existById(anyLong())) - .willReturn(true); + var meeting = MeetingFixture.getCloseMeetingWithId(1L); + var currentUserId = 1L; + given(meetingConnector.findById(anyLong())) + .willReturn(Optional.empty()); // when & then - assertThatThrownBy(() -> userService.registerReview(meetingId, currentUserId, reviewRegisterRequest)) + assertThatThrownBy(() -> userService.registerReview(meeting.getId(), currentUserId, reviewRegisterRequest)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("나의 리뷰에 대한 리뷰를 작성할 수 없습니다."); + .hasMessage("meetingId에 해당하는 모임을 찾을 수 없습니다. \"" + meeting.getId() + "\""); } @Test - @DisplayName("meeting id 에 해당하는 meeting 이 존재하지 않는 경우, 400 Bad Request 와 함께 리뷰 등록을 실패한다.") - void Return_400_bad_request_if_meeting_is_not_exist() { + @DisplayName("meeting id 에 해당하는 meeting 이 아직 종료되지 않았다면, 400 Bad Request 와 함께 리뷰 등록을 실패한다.") + void Return_400_bad_request_if_meeting_is_not_closed() { // given - ReviewRegisterRequest reviewRegisterRequest = RequestFixture.reviewRegisterRequest(); + var reviewRegisterRequest = RequestFixture.reviewRegisterRequest(); + + var meeting = MeetingFixture.getOpenMeetingWithId(1L); + var currentUserId = 1L; - Long meetingId = 1L; - Long currentUserId = 1L; + given(meetingConnector.findById(anyLong())) + .willReturn(Optional.of(meeting)); - given(meetingConnector.existById(anyLong())) - .willReturn(false); // when & then - assertThatThrownBy(() -> userService.registerReview(meetingId, currentUserId, reviewRegisterRequest)) + assertThatThrownBy(() -> userService.registerReview(meeting.getId(), currentUserId, reviewRegisterRequest)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("meetingId에 해당하는 meeting을 찾을 수 없습니다. \"" + meetingId + "\""); + .hasMessage("해당 모임은 아직 종료되지 않았습니다."); } }