From 0d65c2eadabd8ac1ab429be438da7884677b23e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=EC=84=A0?= Date: Tue, 6 Aug 2024 14:16:57 +0900 Subject: [PATCH] =?UTF-8?q?[BE]=20=EB=B0=9C=EC=9E=90=EA=B5=AD=20=EC=82=B0?= =?UTF-8?q?=EC=B1=85=20=EC=83=81=ED=83=9C=20=EB=B3=80=EA=B2=BD=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84=20(#289)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/FootprintController.java | 12 ++ .../footprint/domain/Footprint.java | 40 +++++- .../dto/request/UpdateWalkStatusRequest.java | 19 +++ .../response/UpdateWalkStatusResponse.java | 7 + .../repository/FootprintRepository.java | 6 + .../service/FootprintCommandService.java | 12 ++ .../friendogly/docs/FootprintApiDocsTest.java | 79 ++++++++++ .../controller/FootprintControllerTest.java | 69 ++++++++- .../footprint/domain/FootPrintTest.java | 24 ++-- .../service/FootprintCommandServiceTest.java | 136 ++++++++++++++++++ .../service/FootprintQueryServiceTest.java | 7 +- .../service/FootprintServiceTest.java | 57 ++++++-- 12 files changed, 425 insertions(+), 43 deletions(-) create mode 100644 backend/src/main/java/com/woowacourse/friendogly/footprint/dto/request/UpdateWalkStatusRequest.java create mode 100644 backend/src/main/java/com/woowacourse/friendogly/footprint/dto/response/UpdateWalkStatusResponse.java diff --git a/backend/src/main/java/com/woowacourse/friendogly/footprint/controller/FootprintController.java b/backend/src/main/java/com/woowacourse/friendogly/footprint/controller/FootprintController.java index a856c832d..aa298355d 100644 --- a/backend/src/main/java/com/woowacourse/friendogly/footprint/controller/FootprintController.java +++ b/backend/src/main/java/com/woowacourse/friendogly/footprint/controller/FootprintController.java @@ -4,10 +4,12 @@ import com.woowacourse.friendogly.common.ApiResponse; import com.woowacourse.friendogly.footprint.dto.request.FindNearFootprintRequest; import com.woowacourse.friendogly.footprint.dto.request.SaveFootprintRequest; +import com.woowacourse.friendogly.footprint.dto.request.UpdateWalkStatusRequest; import com.woowacourse.friendogly.footprint.dto.response.FindMyLatestFootprintTimeAndPetExistenceResponse; import com.woowacourse.friendogly.footprint.dto.response.FindNearFootprintResponse; import com.woowacourse.friendogly.footprint.dto.response.FindOneFootprintResponse; import com.woowacourse.friendogly.footprint.dto.response.SaveFootprintResponse; +import com.woowacourse.friendogly.footprint.dto.response.UpdateWalkStatusResponse; import com.woowacourse.friendogly.footprint.service.FootprintCommandService; import com.woowacourse.friendogly.footprint.service.FootprintQueryService; import jakarta.validation.Valid; @@ -15,6 +17,7 @@ import java.util.List; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -75,4 +78,13 @@ public ApiResponse findMyLates = footprintQueryService.findMyLatestFootprintTimeAndPetExistence(memberId); return ApiResponse.ofSuccess(response); } + + @PatchMapping("/walk-status") + public ApiResponse updateWalkStatus( + @Auth Long memberId, + @Valid @RequestBody UpdateWalkStatusRequest request + ) { + UpdateWalkStatusResponse walkStatusResponse = footprintCommandService.updateWalkStatus(memberId, request); + return ApiResponse.ofSuccess(walkStatusResponse); + } } diff --git a/backend/src/main/java/com/woowacourse/friendogly/footprint/domain/Footprint.java b/backend/src/main/java/com/woowacourse/friendogly/footprint/domain/Footprint.java index a2b6387ad..9a0efbc9d 100644 --- a/backend/src/main/java/com/woowacourse/friendogly/footprint/domain/Footprint.java +++ b/backend/src/main/java/com/woowacourse/friendogly/footprint/domain/Footprint.java @@ -1,6 +1,7 @@ package com.woowacourse.friendogly.footprint.domain; -import static com.woowacourse.friendogly.footprint.domain.WalkStatus.BEFORE; +import static com.woowacourse.friendogly.footprint.domain.WalkStatus.AFTER; +import static com.woowacourse.friendogly.footprint.domain.WalkStatus.ONGOING; import com.woowacourse.friendogly.member.domain.Member; import jakarta.persistence.Column; @@ -59,11 +60,15 @@ public Footprint( Member member, Location location ) { - this.member = member; - this.location = location; - this.walkStatus = BEFORE; - this.createdAt = LocalDateTime.now(); - this.isDeleted = false; + this( + member, + location, + WalkStatus.BEFORE, + null, + null, + LocalDateTime.now(), + false + ); } public Footprint( @@ -88,6 +93,10 @@ public boolean isNear(Location location) { return this.location.isWithin(location, NEAR_RADIUS_AS_METER); } + public boolean isInsideBoundary(Location location) { + return this.location.isWithin(location, RADIUS_AS_METER); + } + public boolean isCreatedBy(Long memberId) { return this.member.getId() .equals(memberId); @@ -106,4 +115,23 @@ public LocalDateTime findChangedWalkStatusTime() { } return endWalkTime; } + + public void updateWalkStatusWithCurrentLocation(Location currentLocation) { + if (walkStatus.isBefore() && isInsideBoundary(currentLocation)) { + startWalk(); + } + if (walkStatus.isOngoing() && !isInsideBoundary(currentLocation)) { + endWalk(); + } + } + + private void startWalk() { + walkStatus = ONGOING; + startWalkTime = LocalDateTime.now(); + } + + private void endWalk() { + walkStatus = AFTER; + endWalkTime = LocalDateTime.now(); + } } diff --git a/backend/src/main/java/com/woowacourse/friendogly/footprint/dto/request/UpdateWalkStatusRequest.java b/backend/src/main/java/com/woowacourse/friendogly/footprint/dto/request/UpdateWalkStatusRequest.java new file mode 100644 index 000000000..55097c108 --- /dev/null +++ b/backend/src/main/java/com/woowacourse/friendogly/footprint/dto/request/UpdateWalkStatusRequest.java @@ -0,0 +1,19 @@ +package com.woowacourse.friendogly.footprint.dto.request; + +import jakarta.validation.constraints.DecimalMax; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotNull; + +public record UpdateWalkStatusRequest( + + @NotNull + @DecimalMin(value = "-90.0", message = "위도는 -90도 이상 90도 이하로 입력해 주세요.") + @DecimalMax(value = "90.0", message = "위도는 -90도 이상 90도 이하로 입력해 주세요.") + double latitude, + + @NotNull + @DecimalMin(value = "-180.0", message = "경도는 -180도 이상 180도 이하로 입력해 주세요.") + @DecimalMax(value = "180.0", message = "경도는 -180도 이상 180도 이하로 입력해 주세요.") + double longitude +) { +} diff --git a/backend/src/main/java/com/woowacourse/friendogly/footprint/dto/response/UpdateWalkStatusResponse.java b/backend/src/main/java/com/woowacourse/friendogly/footprint/dto/response/UpdateWalkStatusResponse.java new file mode 100644 index 000000000..d2703f814 --- /dev/null +++ b/backend/src/main/java/com/woowacourse/friendogly/footprint/dto/response/UpdateWalkStatusResponse.java @@ -0,0 +1,7 @@ +package com.woowacourse.friendogly.footprint.dto.response; + +import com.woowacourse.friendogly.footprint.domain.WalkStatus; + +public record UpdateWalkStatusResponse(WalkStatus walkStatus) { + +} diff --git a/backend/src/main/java/com/woowacourse/friendogly/footprint/repository/FootprintRepository.java b/backend/src/main/java/com/woowacourse/friendogly/footprint/repository/FootprintRepository.java index f4141bfac..52d9f643d 100644 --- a/backend/src/main/java/com/woowacourse/friendogly/footprint/repository/FootprintRepository.java +++ b/backend/src/main/java/com/woowacourse/friendogly/footprint/repository/FootprintRepository.java @@ -1,5 +1,6 @@ package com.woowacourse.friendogly.footprint.repository; +import com.woowacourse.friendogly.exception.FriendoglyException; import com.woowacourse.friendogly.footprint.domain.Footprint; import java.time.LocalDateTime; import java.util.List; @@ -13,4 +14,9 @@ public interface FootprintRepository extends JpaRepository { boolean existsByMemberIdAndCreatedAtAfter(Long memberId, LocalDateTime createdAt); Optional findTopOneByMemberIdOrderByCreatedAtDesc(Long memberId); + + default Footprint getTopOneByMemberIdOrderByCreatedAtDesc(Long memberId) { + return findTopOneByMemberIdOrderByCreatedAtDesc(memberId) + .orElseThrow(() -> new FriendoglyException("발자국이 존재하지 않습니다.")); + } } diff --git a/backend/src/main/java/com/woowacourse/friendogly/footprint/service/FootprintCommandService.java b/backend/src/main/java/com/woowacourse/friendogly/footprint/service/FootprintCommandService.java index 6aafdcf89..c76f35b5b 100644 --- a/backend/src/main/java/com/woowacourse/friendogly/footprint/service/FootprintCommandService.java +++ b/backend/src/main/java/com/woowacourse/friendogly/footprint/service/FootprintCommandService.java @@ -4,7 +4,9 @@ import com.woowacourse.friendogly.footprint.domain.Footprint; import com.woowacourse.friendogly.footprint.domain.Location; import com.woowacourse.friendogly.footprint.dto.request.SaveFootprintRequest; +import com.woowacourse.friendogly.footprint.dto.request.UpdateWalkStatusRequest; import com.woowacourse.friendogly.footprint.dto.response.SaveFootprintResponse; +import com.woowacourse.friendogly.footprint.dto.response.UpdateWalkStatusResponse; import com.woowacourse.friendogly.footprint.repository.FootprintRepository; import com.woowacourse.friendogly.member.domain.Member; import com.woowacourse.friendogly.member.repository.MemberRepository; @@ -77,4 +79,14 @@ private void validateRecentFootprintExists(Long memberId) { throw new FriendoglyException(String.format("마지막 발자국을 찍은 뒤 %d초가 경과되지 않았습니다.", FOOTPRINT_COOLDOWN_SECOND)); } } + + public UpdateWalkStatusResponse updateWalkStatus(Long memberId, UpdateWalkStatusRequest request) { + Footprint footprint = footprintRepository.getTopOneByMemberIdOrderByCreatedAtDesc(memberId); + if (footprint.isDeleted()) { + throw new FriendoglyException("가장 최근 발자국이 삭제된 상태입니다."); + } + + footprint.updateWalkStatusWithCurrentLocation(new Location(request.latitude(), request.longitude())); + return new UpdateWalkStatusResponse(footprint.getWalkStatus()); + } } diff --git a/backend/src/test/java/com/woowacourse/friendogly/docs/FootprintApiDocsTest.java b/backend/src/test/java/com/woowacourse/friendogly/docs/FootprintApiDocsTest.java index 54a3a6979..7f9e9b33a 100644 --- a/backend/src/test/java/com/woowacourse/friendogly/docs/FootprintApiDocsTest.java +++ b/backend/src/test/java/com/woowacourse/friendogly/docs/FootprintApiDocsTest.java @@ -3,14 +3,17 @@ import static com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper.document; import static com.epages.restdocs.apispec.ResourceDocumentation.resource; import static com.woowacourse.friendogly.footprint.domain.WalkStatus.BEFORE; +import static com.woowacourse.friendogly.footprint.domain.WalkStatus.ONGOING; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.when; import static org.springframework.http.HttpHeaders.AUTHORIZATION; import static org.springframework.http.HttpHeaders.LOCATION; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.patch; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; @@ -20,12 +23,15 @@ import com.epages.restdocs.apispec.ResourceSnippetParameters; import com.epages.restdocs.apispec.Schema; +import com.woowacourse.friendogly.exception.FriendoglyException; import com.woowacourse.friendogly.footprint.controller.FootprintController; import com.woowacourse.friendogly.footprint.dto.request.SaveFootprintRequest; +import com.woowacourse.friendogly.footprint.dto.request.UpdateWalkStatusRequest; import com.woowacourse.friendogly.footprint.dto.response.FindMyLatestFootprintTimeAndPetExistenceResponse; import com.woowacourse.friendogly.footprint.dto.response.FindNearFootprintResponse; import com.woowacourse.friendogly.footprint.dto.response.FindOneFootprintResponse; import com.woowacourse.friendogly.footprint.dto.response.SaveFootprintResponse; +import com.woowacourse.friendogly.footprint.dto.response.UpdateWalkStatusResponse; import com.woowacourse.friendogly.footprint.dto.response.detail.PetDetail; import com.woowacourse.friendogly.footprint.service.FootprintCommandService; import com.woowacourse.friendogly.footprint.service.FootprintQueryService; @@ -38,6 +44,7 @@ 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.restdocs.payload.JsonFieldType; @WebMvcTest(FootprintController.class) public class FootprintApiDocsTest extends RestDocsTest { @@ -251,6 +258,78 @@ void findMyLatestFootprintTimeAndPetExistence() throws Exception { .andExpect(status().isOk()); } + @DisplayName("발자국 산책 상태 변경") + @Test + void updateWalkStatus_200() throws Exception { + UpdateWalkStatusRequest request = new UpdateWalkStatusRequest(37.5136533, 127.0983182); + UpdateWalkStatusResponse response = new UpdateWalkStatusResponse(ONGOING); + + given(footprintCommandService.updateWalkStatus(any(), any())) + .willReturn(response); + + mockMvc + .perform(patch("/footprints/walk-status") + .content(objectMapper.writeValueAsString(request)) + .contentType(APPLICATION_JSON) + .header(AUTHORIZATION, 1L)) + .andExpect(status().isOk()) + .andDo(print()) + .andDo(document("footprints/walk-status/200", + getDocumentRequest(), + getDocumentResponse(), + resource(ResourceSnippetParameters.builder() + .tag("Footprint API") + .summary("발자국 산책 상태 변경 API") + .requestHeaders( + headerWithName(AUTHORIZATION).description("로그인한 회원 ID") + ) + .responseFields( + fieldWithPath("isSuccess").description("응답 성공 여부"), + fieldWithPath("data.walkStatus").description("발자국 산책 상태") + ) + .requestSchema(Schema.schema("updateWalkStatusResponse")) + .build() + ) + )); + } + + @DisplayName("발자국 산책 상태 변경실패 - 발자국이 없을 경우") + @Test + void updateWalkStatus_400() throws Exception { + UpdateWalkStatusRequest request = new UpdateWalkStatusRequest(37.5136533, 127.0983182); + UpdateWalkStatusResponse response = new UpdateWalkStatusResponse(ONGOING); + + when(footprintCommandService.updateWalkStatus(any(), any())) + .thenThrow(new FriendoglyException("예외 메세지")); + + mockMvc + .perform(patch("/footprints/walk-status") + .content(objectMapper.writeValueAsString(request)) + .contentType(APPLICATION_JSON) + .header(AUTHORIZATION, 1L)) + .andExpect(status().isBadRequest()) + .andDo(print()) + .andDo(document("footprints/walk-status/400", + getDocumentRequest(), + getDocumentResponse(), + resource(ResourceSnippetParameters.builder() + .tag("Footprint API") + .summary("발자국 산책 상태 변경 API") + .requestHeaders( + headerWithName(AUTHORIZATION).description("로그인한 회원 ID") + ) + .responseFields( + fieldWithPath("isSuccess").type(JsonFieldType.BOOLEAN).description("요청 성공 여부"), + fieldWithPath("data.errorCode").type(JsonFieldType.STRING).description("에러 코드"), + fieldWithPath("data.errorMessage").type(JsonFieldType.STRING).description("에러메세지"), + fieldWithPath("data.detail").type(JsonFieldType.ARRAY).description("에러 디테일") + ) + .requestSchema(Schema.schema("updateWalkStatusResponse")) + .build() + ) + )); + } + @Override protected Object controller() { return new FootprintController(footprintCommandService, footprintQueryService); diff --git a/backend/src/test/java/com/woowacourse/friendogly/footprint/controller/FootprintControllerTest.java b/backend/src/test/java/com/woowacourse/friendogly/footprint/controller/FootprintControllerTest.java index c4ca7bb9f..5431a9509 100644 --- a/backend/src/test/java/com/woowacourse/friendogly/footprint/controller/FootprintControllerTest.java +++ b/backend/src/test/java/com/woowacourse/friendogly/footprint/controller/FootprintControllerTest.java @@ -1,5 +1,8 @@ package com.woowacourse.friendogly.footprint.controller; +import static com.woowacourse.friendogly.footprint.domain.WalkStatus.AFTER; +import static com.woowacourse.friendogly.footprint.domain.WalkStatus.BEFORE; +import static com.woowacourse.friendogly.footprint.domain.WalkStatus.ONGOING; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; @@ -19,6 +22,7 @@ import io.restassured.http.ContentType; import java.time.LocalDate; import org.junit.jupiter.api.AfterEach; +import java.time.LocalDateTime; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -118,11 +122,6 @@ void setUp() { ); } - @AfterEach - void setDown() { - footprintRepository.deleteAll(); - } - @DisplayName("발자국을 정상적으로 생성한다. (201)") @Test void save() { @@ -253,4 +252,64 @@ void findMyLatestFootprintTime_MyFootprintDoesNotExist() { .statusCode(HttpStatus.OK.value()) .body("data.changedWalkStatusTime", nullValue()); } + + @DisplayName("발자국 범위밖에서 안으로 들어오면 산책중으로 상태가 변한다 (200)") + @Test + void updateWalkStatus_toOngoing() { + footprintRepository.save( + new Footprint( + member1, + new Location(0,0), + BEFORE, + null, + null, + LocalDateTime.now(), + false + ) + ); + + float latitude = 0.0F; + float longitude = 0.0F; + + SaveFootprintRequest request = new SaveFootprintRequest(latitude, longitude); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .header(HttpHeaders.AUTHORIZATION, member1.getId()) + .body(request) + .when().patch("/footprints/walk-status") + .then().log().all() + .statusCode(HttpStatus.OK.value()) + .body("data.walkStatus", is(ONGOING.toString())); + } + + @DisplayName("발자국 범위안에서 밖으로 나가면 산책후로 상태가 변한다 (200)") + @Test + void updateWalkStatus_toAfter(){ + footprintRepository.save( + new Footprint( + member1, + new Location(0,0), + ONGOING, + LocalDateTime.now(), + null, + LocalDateTime.now().minusHours(1), + false + ) + ); + + float latitude = 37.0F; + float longitude = 127.0F; + + SaveFootprintRequest request = new SaveFootprintRequest(latitude, longitude); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .header(HttpHeaders.AUTHORIZATION, member1.getId()) + .body(request) + .when().patch("/footprints/walk-status") + .then().log().all() + .statusCode(HttpStatus.OK.value()) + .body("data.walkStatus",is(AFTER.toString())); + } } diff --git a/backend/src/test/java/com/woowacourse/friendogly/footprint/domain/FootPrintTest.java b/backend/src/test/java/com/woowacourse/friendogly/footprint/domain/FootPrintTest.java index 9e7b537f4..dd4e86709 100644 --- a/backend/src/test/java/com/woowacourse/friendogly/footprint/domain/FootPrintTest.java +++ b/backend/src/test/java/com/woowacourse/friendogly/footprint/domain/FootPrintTest.java @@ -21,14 +21,14 @@ public class FootPrintTest { @Test void findChangedWalkStatusTime_when_WalkStatusIsBefore(){ // given - LocalDateTime expectedTime = LocalDateTime.of(2023, 12, 12, 10, 30); + LocalDateTime expectedChangedWalkStatusTime = LocalDateTime.of(2023, 12, 12, 10, 30); Footprint footprint = new Footprint( member, new Location(30, 30), BEFORE, null, null, - expectedTime, + expectedChangedWalkStatusTime, false ); @@ -36,21 +36,21 @@ void findChangedWalkStatusTime_when_WalkStatusIsBefore(){ LocalDateTime changedWalkStatusTime = footprint.findChangedWalkStatusTime(); // then - assertThat(changedWalkStatusTime).isEqualTo(expectedTime); + assertThat(changedWalkStatusTime).isEqualTo(expectedChangedWalkStatusTime); } @DisplayName("산책중일경우, 산책 상태 변경된 시간은 산책 시작 시간이 반환된다.") @Test void findChangedWalkStatusTime_when_WalkStatusIsOngoing(){ // given - LocalDateTime expectedTime = LocalDateTime.of(2023, 12, 12, 10, 30); + LocalDateTime expectedChangedWalkStatusTime = LocalDateTime.of(2023, 12, 12, 10, 30); Footprint footprint = new Footprint( member, new Location(30, 30), ONGOING, - expectedTime, + expectedChangedWalkStatusTime, null, - expectedTime.minusHours(2), + expectedChangedWalkStatusTime.minusHours(2), false ); @@ -58,21 +58,21 @@ void findChangedWalkStatusTime_when_WalkStatusIsOngoing(){ LocalDateTime changedWalkStatusTime = footprint.findChangedWalkStatusTime(); // then - assertThat(changedWalkStatusTime).isEqualTo(expectedTime); + assertThat(changedWalkStatusTime).isEqualTo(expectedChangedWalkStatusTime); } @DisplayName("산책후일경우, 산책 상태 변경된 시간은 산책 종료 시간이 반환된다.") @Test void findChangedWalkStatusTime_when_WalkStatusIsAfter(){ // given - LocalDateTime expectedTime = LocalDateTime.of(2023, 12, 12, 10, 30); + LocalDateTime expectedChangedWalkStatusTime = LocalDateTime.of(2023, 12, 12, 10, 30); Footprint footprint = new Footprint( member, new Location(30, 30), AFTER, - expectedTime.minusHours(1), - expectedTime, - expectedTime.minusHours(2), + expectedChangedWalkStatusTime.minusHours(1), + expectedChangedWalkStatusTime, + expectedChangedWalkStatusTime.minusHours(2), false ); @@ -80,6 +80,6 @@ void findChangedWalkStatusTime_when_WalkStatusIsAfter(){ LocalDateTime changedWalkStatusTime = footprint.findChangedWalkStatusTime(); // then - assertThat(changedWalkStatusTime).isEqualTo(expectedTime); + assertThat(changedWalkStatusTime).isEqualTo(expectedChangedWalkStatusTime); } } diff --git a/backend/src/test/java/com/woowacourse/friendogly/footprint/service/FootprintCommandServiceTest.java b/backend/src/test/java/com/woowacourse/friendogly/footprint/service/FootprintCommandServiceTest.java index 8c3e10668..7a3677683 100644 --- a/backend/src/test/java/com/woowacourse/friendogly/footprint/service/FootprintCommandServiceTest.java +++ b/backend/src/test/java/com/woowacourse/friendogly/footprint/service/FootprintCommandServiceTest.java @@ -1,11 +1,16 @@ package com.woowacourse.friendogly.footprint.service; +import static com.woowacourse.friendogly.footprint.domain.WalkStatus.AFTER; +import static com.woowacourse.friendogly.footprint.domain.WalkStatus.BEFORE; +import static com.woowacourse.friendogly.footprint.domain.WalkStatus.ONGOING; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.woowacourse.friendogly.exception.FriendoglyException; import com.woowacourse.friendogly.footprint.domain.Footprint; +import com.woowacourse.friendogly.footprint.domain.Location; import com.woowacourse.friendogly.footprint.dto.request.SaveFootprintRequest; +import com.woowacourse.friendogly.footprint.dto.request.UpdateWalkStatusRequest; import com.woowacourse.friendogly.member.domain.Member; import java.time.LocalDateTime; import org.junit.jupiter.api.DisplayName; @@ -89,4 +94,135 @@ void save_With_DeleteRecentFootprint() { // then assertThat(recentFootprint.isDeleted()).isTrue(); } + + @DisplayName("발자국 상태 변경 실패 - 발자국 존재하지 않음") + @Test + void updateWalkStatus_fail_nonExistFootprint() { + // when - then + assertThatThrownBy(() -> footprintCommandService.updateWalkStatus( + member.getId(), + new UpdateWalkStatusRequest(0, 0) + ) + ).isExactlyInstanceOf(FriendoglyException.class) + .hasMessage("발자국이 존재하지 않습니다."); + } + + @DisplayName("발자국 상태 변경 실패 - 가장 최근 발자국이 삭제된(logical) 발자국") + @Test + void updateWalkStatus_fail_logicalDeletedFootprint() { + // given + footprintRepository.save(FOOTPRINT_DELETED()); + + // when - then + assertThatThrownBy(() -> footprintCommandService.updateWalkStatus( + member.getId(), + new UpdateWalkStatusRequest(0, 0) + ) + ).isExactlyInstanceOf(FriendoglyException.class) + .hasMessage("가장 최근 발자국이 삭제된 상태입니다."); + } + + @DisplayName("산책 전이고 현재위치가 범위 안이면 산책 중으로 변한다") + @Transactional + @Test + void updateWalkStatus_beforeToOngoing() { + // given + Footprint savedFootprint = footprintRepository.save(FOOTPRINT_STATUS_BEFORE(new Location(0, 0))); + + // when + footprintCommandService.updateWalkStatus( + member.getId(), + new UpdateWalkStatusRequest(0.0, LONGITUDE_WITH_METER_FROM_ZERO(999)) + ); + + // then + assertThat(savedFootprint.getWalkStatus()).isEqualTo(ONGOING); + } + + @DisplayName("산책 전이고 현재위치가 범위 밖이면 산책 상태 변화는 없다") + @Transactional + @Test + void updateWalkStatus_beforeNoChange() { + // given + Footprint savedFootprint = footprintRepository.save(FOOTPRINT_STATUS_BEFORE(new Location(0, 0))); + + // when + footprintCommandService.updateWalkStatus( + member.getId(), + new UpdateWalkStatusRequest(0.0, LONGITUDE_WITH_METER_FROM_ZERO(1001)) + ); + + // then + assertThat(savedFootprint.getWalkStatus()).isEqualTo(BEFORE); + } + + @DisplayName("산책 중이고 현재위치가 범위 밖이면 산책 후로 변한다") + @Transactional + @Test + void updateWalkStatus_ongoingToAfter() { + // given + Footprint savedFootprint = footprintRepository.save(FOOTPRINT_STATUS_ONGOING(new Location(0, 0))); + + // when + footprintCommandService.updateWalkStatus( + member.getId(), + new UpdateWalkStatusRequest(0.0, LONGITUDE_WITH_METER_FROM_ZERO(1001)) + ); + + // then + assertThat(savedFootprint.getWalkStatus()).isEqualTo(AFTER); + } + + + @DisplayName("산책 중이고 현재위치가 범위 안이면 산책 상태 변화는 없다") + @Transactional + @Test + void updateWalkStatus_ongoingNoChange() { + // given + Footprint savedFootprint = footprintRepository.save(FOOTPRINT_STATUS_ONGOING(new Location(0, 0))); + + // when + footprintCommandService.updateWalkStatus( + member.getId(), + new UpdateWalkStatusRequest(0.0, LONGITUDE_WITH_METER_FROM_ZERO(999)) + ); + + // then + assertThat(savedFootprint.getWalkStatus()).isEqualTo(ONGOING); + } + + + @DisplayName("산책 후이고, 현재위치가 범위 안이면 산책 상태 변화는 없다") + @Transactional + @Test + void updateWalkStatus_ongoingNoChange_inside() { + // given + Footprint savedFootprint = footprintRepository.save(FOOTPRINT_STATUS_AFTER(new Location(0, 0))); + + // when + footprintCommandService.updateWalkStatus( + member.getId(), + new UpdateWalkStatusRequest(0.0, LONGITUDE_WITH_METER_FROM_ZERO(999)) + ); + + // then + assertThat(savedFootprint.getWalkStatus()).isEqualTo(AFTER); + } + + @DisplayName("산책 후이고, 현재위치가 범위 밖이면 산책 상태 변화는 없다") + @Transactional + @Test + void updateWalkStatus_afterNoChange_outside() { + // given + Footprint savedFootprint = footprintRepository.save(FOOTPRINT_STATUS_AFTER(new Location(0, 0))); + + // when + footprintCommandService.updateWalkStatus( + member.getId(), + new UpdateWalkStatusRequest(0.0, LONGITUDE_WITH_METER_FROM_ZERO(1001)) + ); + + // then + assertThat(savedFootprint.getWalkStatus()).isEqualTo(AFTER); + } } diff --git a/backend/src/test/java/com/woowacourse/friendogly/footprint/service/FootprintQueryServiceTest.java b/backend/src/test/java/com/woowacourse/friendogly/footprint/service/FootprintQueryServiceTest.java index ee7e27eef..c1421291a 100644 --- a/backend/src/test/java/com/woowacourse/friendogly/footprint/service/FootprintQueryServiceTest.java +++ b/backend/src/test/java/com/woowacourse/friendogly/footprint/service/FootprintQueryServiceTest.java @@ -113,12 +113,7 @@ void findNear24Hours() { member.getId(), new FindNearFootprintRequest(0.0, 0.0)); // then - assertAll( - () -> assertThat(nearFootprints).extracting(FindNearFootprintResponse::latitude) - .containsExactly(0.00000, 0.00000), - () -> assertThat(nearFootprints).extracting(FindNearFootprintResponse::longitude) - .containsExactly(0.00000, 0.00000) - ); + assertThat(nearFootprints.size()).isEqualTo(2); } @DisplayName("주변 발자국 조회시 삭제된 발자국은 조회되지 않는다.") diff --git a/backend/src/test/java/com/woowacourse/friendogly/footprint/service/FootprintServiceTest.java b/backend/src/test/java/com/woowacourse/friendogly/footprint/service/FootprintServiceTest.java index ae951644b..db6847ca9 100644 --- a/backend/src/test/java/com/woowacourse/friendogly/footprint/service/FootprintServiceTest.java +++ b/backend/src/test/java/com/woowacourse/friendogly/footprint/service/FootprintServiceTest.java @@ -2,6 +2,7 @@ import static com.woowacourse.friendogly.footprint.domain.WalkStatus.AFTER; import static com.woowacourse.friendogly.footprint.domain.WalkStatus.BEFORE; +import static com.woowacourse.friendogly.footprint.domain.WalkStatus.ONGOING; import com.woowacourse.friendogly.footprint.domain.Footprint; import com.woowacourse.friendogly.footprint.domain.Location; @@ -59,16 +60,14 @@ protected Footprint FOOTPRINT() { return new Footprint(member, new Location(0, 0)); } - protected Footprint FOOTPRINT(Member member) { - return new Footprint( - member, + protected Footprint FOOTPRINT_DELETED() { + return new Footprint(member, new Location(0, 0), BEFORE, null, null, LocalDateTime.now(), - false - ); + true); } protected Footprint FOOTPRINT(LocalDateTime createdAt) { @@ -83,15 +82,15 @@ protected Footprint FOOTPRINT(LocalDateTime createdAt) { ); } - protected Footprint FOOTPRINT_DELETED() { + protected Footprint FOOTPRINT_STATUS_BEFORE(Location location) { return new Footprint( member, - new Location(0, 0), + location, BEFORE, null, null, LocalDateTime.now(), - true + false ); } @@ -107,14 +106,14 @@ protected Footprint FOOTPRINT_STATUS_BEFORE(LocalDateTime createdAt) { ); } - protected Footprint FOOTPRINT_STATUS_AFTER(LocalDateTime createdAt) { + protected Footprint FOOTPRINT_STATUS_ONGOING(Location location) { return new Footprint( member, - new Location(0, 0), - AFTER, - null, + location, + ONGOING, + LocalDateTime.now(), null, - createdAt, + LocalDateTime.now().minusHours(1), false ); } @@ -124,10 +123,40 @@ protected Footprint FOOTPRINT_STATUS_ONGOING(LocalDateTime createdAt) { member, new Location(0, 0), BEFORE, + LocalDateTime.now(), null, - null, createdAt, false ); } + + protected Footprint FOOTPRINT_STATUS_AFTER(Location location) { + return new Footprint( + member, + location, + AFTER, + LocalDateTime.now().minusHours(1), + LocalDateTime.now(), + LocalDateTime.now().minusHours(2), + false + ); + } + + protected Footprint FOOTPRINT_STATUS_AFTER(LocalDateTime createdAt) { + return new Footprint( + member, + new Location(0, 0), + AFTER, + LocalDateTime.now().minusHours(1), + LocalDateTime.now(), + createdAt, + false + ); + } + + protected static double ONE_METER_LOCATION_UNIT = 0.0000089847; + + protected static double LONGITUDE_WITH_METER_FROM_ZERO(double meter) { + return meter * ONE_METER_LOCATION_UNIT; + } }