diff --git a/backend/src/main/java/com/happy/friendogly/common/ErrorCode.java b/backend/src/main/java/com/happy/friendogly/common/ErrorCode.java index 89c90bbbb..9646a4a03 100644 --- a/backend/src/main/java/com/happy/friendogly/common/ErrorCode.java +++ b/backend/src/main/java/com/happy/friendogly/common/ErrorCode.java @@ -2,10 +2,10 @@ public enum ErrorCode { - // TODO: 추가 필요 DEFAULT_ERROR_CODE, FAILED_LOAD_SERVER_FIREBASE_CREDENTIALS, INVALID_FIREBASE_CREDENTIALS, FILE_SIZE_EXCEED, + NOT_ALLOW_OTHER_FOOTPRINT_CHANGE, ; } diff --git a/backend/src/main/java/com/happy/friendogly/footprint/controller/FootprintController.java b/backend/src/main/java/com/happy/friendogly/footprint/controller/FootprintController.java index 202509e24..4f12da5be 100644 --- a/backend/src/main/java/com/happy/friendogly/footprint/controller/FootprintController.java +++ b/backend/src/main/java/com/happy/friendogly/footprint/controller/FootprintController.java @@ -4,18 +4,20 @@ import com.happy.friendogly.common.ApiResponse; import com.happy.friendogly.footprint.dto.request.FindNearFootprintRequest; import com.happy.friendogly.footprint.dto.request.SaveFootprintRequest; -import com.happy.friendogly.footprint.dto.request.UpdateWalkStatusRequest; +import com.happy.friendogly.footprint.dto.request.UpdateWalkStatusAutoRequest; import com.happy.friendogly.footprint.dto.response.FindMyLatestFootprintTimeAndPetExistenceResponse; import com.happy.friendogly.footprint.dto.response.FindNearFootprintResponse; import com.happy.friendogly.footprint.dto.response.FindOneFootprintResponse; import com.happy.friendogly.footprint.dto.response.SaveFootprintResponse; -import com.happy.friendogly.footprint.dto.response.UpdateWalkStatusResponse; +import com.happy.friendogly.footprint.dto.response.UpdateWalkStatusAutoResponse; +import com.happy.friendogly.footprint.dto.response.UpdateWalkStatusManualResponse; import com.happy.friendogly.footprint.service.FootprintCommandService; import com.happy.friendogly.footprint.service.FootprintQueryService; import jakarta.validation.Valid; import java.net.URI; import java.util.List; 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.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -54,7 +56,6 @@ public ApiResponse findOne( @Auth Long memberId, @PathVariable Long footprintId ) { - // TODO: 추후 토큰에서 memberId를 가져오도록 변경 FindOneFootprintResponse response = footprintQueryService.findOne(memberId, footprintId); return ApiResponse.ofSuccess(response); } @@ -64,7 +65,6 @@ public ApiResponse> findNear( @Auth Long memberId, @Valid FindNearFootprintRequest request ) { - // TODO: 추후 토큰에서 memberId를 가져오도록 변경 List response = footprintQueryService.findNear(memberId, request); return ApiResponse.ofSuccess(response); } @@ -73,18 +73,34 @@ public ApiResponse> findNear( public ApiResponse findMyLatestFootprintTimeAndPetExistence( @Auth Long memberId ) { - // TODO: 추후 토큰에서 memberId를 가져오도록 변경 FindMyLatestFootprintTimeAndPetExistenceResponse response = footprintQueryService.findMyLatestFootprintTimeAndPetExistence(memberId); return ApiResponse.ofSuccess(response); } - @PatchMapping("/walk-status") - public ApiResponse updateWalkStatus( + @PatchMapping("/recent/walk-status/auto") + public ApiResponse updateWalkStatusAuto( @Auth Long memberId, - @Valid @RequestBody UpdateWalkStatusRequest request + @Valid @RequestBody UpdateWalkStatusAutoRequest request ) { - UpdateWalkStatusResponse walkStatusResponse = footprintCommandService.updateWalkStatus(memberId, request); - return ApiResponse.ofSuccess(walkStatusResponse); + UpdateWalkStatusAutoResponse response = footprintCommandService.updateWalkStatusAuto(memberId, request); + return ApiResponse.ofSuccess(response); + } + + @PatchMapping("/recent/walk-status/manual") + public ApiResponse updateWalkStatusManual( + @Auth Long memberId + ) { + UpdateWalkStatusManualResponse response = footprintCommandService.updateWalkStatusManual(memberId); + return ApiResponse.ofSuccess(response); + } + + @DeleteMapping("/{footprintId}") + public ResponseEntity delete( + @Auth Long memberId, + @PathVariable Long footprintId + ) { + footprintCommandService.delete(memberId, footprintId); + return ResponseEntity.noContent().build(); } } diff --git a/backend/src/main/java/com/happy/friendogly/footprint/domain/Footprint.java b/backend/src/main/java/com/happy/friendogly/footprint/domain/Footprint.java index 53e436e9e..3484012db 100644 --- a/backend/src/main/java/com/happy/friendogly/footprint/domain/Footprint.java +++ b/backend/src/main/java/com/happy/friendogly/footprint/domain/Footprint.java @@ -3,6 +3,7 @@ import static com.happy.friendogly.footprint.domain.WalkStatus.AFTER; import static com.happy.friendogly.footprint.domain.WalkStatus.ONGOING; +import com.happy.friendogly.exception.FriendoglyException; import com.happy.friendogly.member.domain.Member; import jakarta.persistence.Column; import jakarta.persistence.Embedded; @@ -126,6 +127,13 @@ public void updateWalkStatusWithCurrentLocation(Location currentLocation) { } } + public void finishWalkingManual() { + if (walkStatus.isBefore() || walkStatus.isAfter()) { + throw new FriendoglyException("산책 중에서 산책 후로의 변경만 가능합니다."); + } + endWalk(); + } + private void startWalk() { walkStatus = ONGOING; startWalkTime = LocalDateTime.now(); diff --git a/backend/src/main/java/com/happy/friendogly/footprint/dto/request/UpdateWalkStatusRequest.java b/backend/src/main/java/com/happy/friendogly/footprint/dto/request/UpdateWalkStatusAutoRequest.java similarity index 94% rename from backend/src/main/java/com/happy/friendogly/footprint/dto/request/UpdateWalkStatusRequest.java rename to backend/src/main/java/com/happy/friendogly/footprint/dto/request/UpdateWalkStatusAutoRequest.java index e4f0b6153..2ecb93275 100644 --- a/backend/src/main/java/com/happy/friendogly/footprint/dto/request/UpdateWalkStatusRequest.java +++ b/backend/src/main/java/com/happy/friendogly/footprint/dto/request/UpdateWalkStatusAutoRequest.java @@ -4,7 +4,7 @@ import jakarta.validation.constraints.DecimalMin; import jakarta.validation.constraints.NotNull; -public record UpdateWalkStatusRequest( +public record UpdateWalkStatusAutoRequest( @NotNull @DecimalMin(value = "-90.0", message = "위도는 -90도 이상 90도 이하로 입력해 주세요.") diff --git a/backend/src/main/java/com/happy/friendogly/footprint/dto/request/UpdateWalkStatusManualRequest.java b/backend/src/main/java/com/happy/friendogly/footprint/dto/request/UpdateWalkStatusManualRequest.java new file mode 100644 index 000000000..46cff8c60 --- /dev/null +++ b/backend/src/main/java/com/happy/friendogly/footprint/dto/request/UpdateWalkStatusManualRequest.java @@ -0,0 +1,11 @@ +package com.happy.friendogly.footprint.dto.request; + +import jakarta.validation.constraints.NotBlank; + +public record UpdateWalkStatusManualRequest( + + @NotBlank(message = "walkStatus는 빈 문자열이나 null을 입력할 수 없습니다.") + String walkStatus +) { + +} diff --git a/backend/src/main/java/com/happy/friendogly/footprint/dto/response/UpdateWalkStatusAutoResponse.java b/backend/src/main/java/com/happy/friendogly/footprint/dto/response/UpdateWalkStatusAutoResponse.java new file mode 100644 index 000000000..539064248 --- /dev/null +++ b/backend/src/main/java/com/happy/friendogly/footprint/dto/response/UpdateWalkStatusAutoResponse.java @@ -0,0 +1,11 @@ +package com.happy.friendogly.footprint.dto.response; + +import com.happy.friendogly.footprint.domain.WalkStatus; +import java.time.LocalDateTime; + +public record UpdateWalkStatusAutoResponse( + WalkStatus walkStatus, + LocalDateTime changedWalkStatusTime +) { + +} diff --git a/backend/src/main/java/com/happy/friendogly/footprint/dto/response/UpdateWalkStatusManualResponse.java b/backend/src/main/java/com/happy/friendogly/footprint/dto/response/UpdateWalkStatusManualResponse.java new file mode 100644 index 000000000..19e4e6414 --- /dev/null +++ b/backend/src/main/java/com/happy/friendogly/footprint/dto/response/UpdateWalkStatusManualResponse.java @@ -0,0 +1,11 @@ +package com.happy.friendogly.footprint.dto.response; + +import com.happy.friendogly.footprint.domain.WalkStatus; +import java.time.LocalDateTime; + +public record UpdateWalkStatusManualResponse( + WalkStatus walkStatus, + LocalDateTime changedWalkStatusTime +) { + +} diff --git a/backend/src/main/java/com/happy/friendogly/footprint/dto/response/UpdateWalkStatusResponse.java b/backend/src/main/java/com/happy/friendogly/footprint/dto/response/UpdateWalkStatusResponse.java deleted file mode 100644 index 58a0564da..000000000 --- a/backend/src/main/java/com/happy/friendogly/footprint/dto/response/UpdateWalkStatusResponse.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.happy.friendogly.footprint.dto.response; - -import com.happy.friendogly.footprint.domain.WalkStatus; - -public record UpdateWalkStatusResponse(WalkStatus walkStatus) { - -} diff --git a/backend/src/main/java/com/happy/friendogly/footprint/repository/FootprintRepository.java b/backend/src/main/java/com/happy/friendogly/footprint/repository/FootprintRepository.java index fe5ef65cf..54270856d 100644 --- a/backend/src/main/java/com/happy/friendogly/footprint/repository/FootprintRepository.java +++ b/backend/src/main/java/com/happy/friendogly/footprint/repository/FootprintRepository.java @@ -21,4 +21,9 @@ default Footprint getTopOneByMemberIdOrderByCreatedAtDesc(Long memberId) { } List findAllByIsDeletedFalse(); + + default Footprint getById(Long id) { + return findById(id) + .orElseThrow(() -> new FriendoglyException("발자국이 존재하지 않습니다.")); + } } diff --git a/backend/src/main/java/com/happy/friendogly/footprint/service/FootprintCommandService.java b/backend/src/main/java/com/happy/friendogly/footprint/service/FootprintCommandService.java index 53cd0b6eb..bd5dd052a 100644 --- a/backend/src/main/java/com/happy/friendogly/footprint/service/FootprintCommandService.java +++ b/backend/src/main/java/com/happy/friendogly/footprint/service/FootprintCommandService.java @@ -5,9 +5,10 @@ import com.happy.friendogly.footprint.domain.Location; import com.happy.friendogly.footprint.domain.WalkStatus; import com.happy.friendogly.footprint.dto.request.SaveFootprintRequest; -import com.happy.friendogly.footprint.dto.request.UpdateWalkStatusRequest; +import com.happy.friendogly.footprint.dto.request.UpdateWalkStatusAutoRequest; import com.happy.friendogly.footprint.dto.response.SaveFootprintResponse; -import com.happy.friendogly.footprint.dto.response.UpdateWalkStatusResponse; +import com.happy.friendogly.footprint.dto.response.UpdateWalkStatusAutoResponse; +import com.happy.friendogly.footprint.dto.response.UpdateWalkStatusManualResponse; import com.happy.friendogly.footprint.repository.FootprintRepository; import com.happy.friendogly.member.domain.Member; import com.happy.friendogly.member.repository.MemberRepository; @@ -103,7 +104,7 @@ private void sendWalkComingNotification(String memberName, List nearDevi ); } - public UpdateWalkStatusResponse updateWalkStatus(Long memberId, UpdateWalkStatusRequest request) { + public UpdateWalkStatusAutoResponse updateWalkStatusAuto(Long memberId, UpdateWalkStatusAutoRequest request) { Footprint footprint = footprintRepository.getTopOneByMemberIdOrderByCreatedAtDesc(memberId); if (footprint.isDeleted()) { throw new FriendoglyException("가장 최근 발자국이 삭제된 상태입니다."); @@ -112,6 +113,7 @@ public UpdateWalkStatusResponse updateWalkStatus(Long memberId, UpdateWalkStatus WalkStatus beforeWalkStatus = footprint.getWalkStatus(); footprint.updateWalkStatusWithCurrentLocation(new Location(request.latitude(), request.longitude())); + if (beforeWalkStatus.isBefore() && footprint.getWalkStatus().isOngoing()) { Member startWalkMember = memberRepository.getById(memberId); List nearDeviceTokens = findNearDeviceTokensWithoutMine(footprint, startWalkMember); @@ -119,7 +121,7 @@ public UpdateWalkStatusResponse updateWalkStatus(Long memberId, UpdateWalkStatus sendWalkStartNotification(memberName, nearDeviceTokens); } - return new UpdateWalkStatusResponse(footprint.getWalkStatus()); + return new UpdateWalkStatusAutoResponse(footprint.getWalkStatus(), footprint.findChangedWalkStatusTime()); } private void sendWalkStartNotification(String startMemberName, List nearDeviceTokens) { @@ -130,6 +132,20 @@ private void sendWalkStartNotification(String startMemberName, List near ); } + public UpdateWalkStatusManualResponse updateWalkStatusManual(Long memberId) { + Footprint footprint = footprintRepository.getTopOneByMemberIdOrderByCreatedAtDesc(memberId); + footprint.finishWalkingManual(); + return new UpdateWalkStatusManualResponse(footprint.getWalkStatus(), footprint.findChangedWalkStatusTime()); + } + + public void delete(Long memberId, Long footprintId) { + Footprint footprint = footprintRepository.getById(footprintId); + if (!footprint.isCreatedBy(memberId)) { + throw new FriendoglyException("본인의 발자국만 삭제 가능합니다."); + } + footprint.updateToDeleted(); + } + private List findNearDeviceTokensWithoutMine(Footprint standardFootprint, Member member) { List footprints = footprintRepository.findAllByIsDeletedFalse(); diff --git a/backend/src/test/java/com/happy/friendogly/docs/FootprintApiDocsTest.java b/backend/src/test/java/com/happy/friendogly/docs/FootprintApiDocsTest.java index 8e2d20e1d..80cdc41bc 100644 --- a/backend/src/test/java/com/happy/friendogly/docs/FootprintApiDocsTest.java +++ b/backend/src/test/java/com/happy/friendogly/docs/FootprintApiDocsTest.java @@ -2,16 +2,19 @@ import static com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper.document; import static com.epages.restdocs.apispec.ResourceDocumentation.resource; +import static com.happy.friendogly.footprint.domain.WalkStatus.AFTER; import static com.happy.friendogly.footprint.domain.WalkStatus.BEFORE; import static com.happy.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.doNothing; 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.delete; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.patch; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; @@ -26,12 +29,14 @@ import com.happy.friendogly.exception.FriendoglyException; import com.happy.friendogly.footprint.controller.FootprintController; import com.happy.friendogly.footprint.dto.request.SaveFootprintRequest; -import com.happy.friendogly.footprint.dto.request.UpdateWalkStatusRequest; +import com.happy.friendogly.footprint.dto.request.UpdateWalkStatusAutoRequest; +import com.happy.friendogly.footprint.dto.request.UpdateWalkStatusManualRequest; import com.happy.friendogly.footprint.dto.response.FindMyLatestFootprintTimeAndPetExistenceResponse; import com.happy.friendogly.footprint.dto.response.FindNearFootprintResponse; import com.happy.friendogly.footprint.dto.response.FindOneFootprintResponse; import com.happy.friendogly.footprint.dto.response.SaveFootprintResponse; -import com.happy.friendogly.footprint.dto.response.UpdateWalkStatusResponse; +import com.happy.friendogly.footprint.dto.response.UpdateWalkStatusAutoResponse; +import com.happy.friendogly.footprint.dto.response.UpdateWalkStatusManualResponse; import com.happy.friendogly.footprint.dto.response.detail.PetDetail; import com.happy.friendogly.footprint.service.FootprintCommandService; import com.happy.friendogly.footprint.service.FootprintQueryService; @@ -257,34 +262,35 @@ void findMyLatestFootprintTimeAndPetExistence() throws Exception { .andExpect(status().isOk()); } - @DisplayName("발자국 산책 상태 변경") + @DisplayName("발자국 산책 상태 변경(현재위치기반)") @Test - void updateWalkStatus_200() throws Exception { - UpdateWalkStatusRequest request = new UpdateWalkStatusRequest(37.5136533, 127.0983182); - UpdateWalkStatusResponse response = new UpdateWalkStatusResponse(ONGOING); + void updateWalkStatusAuto_200() throws Exception { + UpdateWalkStatusAutoRequest request = new UpdateWalkStatusAutoRequest(37.5136533, 127.0983182); + UpdateWalkStatusAutoResponse response = new UpdateWalkStatusAutoResponse(ONGOING, LocalDateTime.now()); - given(footprintCommandService.updateWalkStatus(any(), any())) + given(footprintCommandService.updateWalkStatusAuto(any(), any())) .willReturn(response); mockMvc - .perform(patch("/footprints/walk-status") + .perform(patch("/footprints/recent/walk-status/auto") .content(objectMapper.writeValueAsString(request)) .contentType(APPLICATION_JSON) .header(AUTHORIZATION, getMemberToken())) .andExpect(status().isOk()) .andDo(print()) - .andDo(document("footprints/walk-status/200", + .andDo(document("footprints/recent/walk-status/auto/200", getDocumentRequest(), getDocumentResponse(), resource(ResourceSnippetParameters.builder() .tag("Footprint API") - .summary("발자국 산책 상태 변경 API") + .summary("발자국 산책 상태 변경(auto) API") .requestHeaders( headerWithName(AUTHORIZATION).description("로그인한 회원의 accessToken") ) .responseFields( fieldWithPath("isSuccess").description("응답 성공 여부"), - fieldWithPath("data.walkStatus").description("발자국 산책 상태") + fieldWithPath("data.walkStatus").description("발자국 산책 상태"), + fieldWithPath("data.changedWalkStatusTime").description("산책 상태 변경 시간") ) .requestSchema(Schema.schema("updateWalkStatusResponse")) .build() @@ -292,28 +298,27 @@ void updateWalkStatus_200() throws Exception { )); } - @DisplayName("발자국 산책 상태 변경실패 - 발자국이 없을 경우") + @DisplayName("발자국 산책 상태 변경(현재위치기반) - 발자국이 없을 경우") @Test - void updateWalkStatus_400() throws Exception { - UpdateWalkStatusRequest request = new UpdateWalkStatusRequest(37.5136533, 127.0983182); - UpdateWalkStatusResponse response = new UpdateWalkStatusResponse(ONGOING); + void updateWalkStatusAuto_400() throws Exception { + UpdateWalkStatusAutoRequest request = new UpdateWalkStatusAutoRequest(37.5136533, 127.0983182); - when(footprintCommandService.updateWalkStatus(any(), any())) + when(footprintCommandService.updateWalkStatusAuto(any(), any())) .thenThrow(new FriendoglyException("예외 메세지")); mockMvc - .perform(patch("/footprints/walk-status") + .perform(patch("/footprints/recent/walk-status/auto") .content(objectMapper.writeValueAsString(request)) .contentType(APPLICATION_JSON) .header(AUTHORIZATION, getMemberToken())) .andExpect(status().isBadRequest()) .andDo(print()) - .andDo(document("footprints/walk-status/400", + .andDo(document("footprints/recent/walk-status/auto/400", getDocumentRequest(), getDocumentResponse(), resource(ResourceSnippetParameters.builder() .tag("Footprint API") - .summary("발자국 산책 상태 변경 API") + .summary("발자국 산책 상태 변경(auto) API") .requestHeaders( headerWithName(AUTHORIZATION).description("로그인한 회원의 accessToken") ) @@ -330,6 +335,66 @@ void updateWalkStatus_400() throws Exception { )); } + @DisplayName("발자국 산책 상태 변경(수동)") + @Test + void updateWalkStatusManual_200() throws Exception { + UpdateWalkStatusManualRequest request = new UpdateWalkStatusManualRequest(AFTER.toString()); + UpdateWalkStatusManualResponse response = new UpdateWalkStatusManualResponse(AFTER, LocalDateTime.now()); + + given(footprintCommandService.updateWalkStatusManual(any())) + .willReturn(response); + + mockMvc + .perform(patch("/footprints/recent/walk-status/manual") + .content(objectMapper.writeValueAsString(request)) + .contentType(APPLICATION_JSON) + .header(AUTHORIZATION, getMemberToken())) + .andExpect(status().isOk()) + .andDo(print()) + .andDo(document("footprints/recent/walk-status/manual/200", + getDocumentRequest(), + getDocumentResponse(), + resource(ResourceSnippetParameters.builder() + .tag("Footprint API") + .summary("발자국 산책 상태 변경(manual) API") + .requestHeaders( + headerWithName(AUTHORIZATION).description("로그인한 회원의 accessToken") + ) + .responseFields( + fieldWithPath("isSuccess").description("응답 성공 여부"), + fieldWithPath("data.walkStatus").description("발자국 산책 상태"), + fieldWithPath("data.changedWalkStatusTime").description("산책 상태 변경 시간") + ) + .requestSchema(Schema.schema("updateWalkStatusAutoResponse")) + .build() + ) + )); + } + + @DisplayName("발자국 삭제") + @Test + void deleteFootprint() throws Exception { + doNothing().when(footprintCommandService).delete(any(), any()); + + mockMvc + .perform(delete("/footprints/{footprintId}", 1) + .header(AUTHORIZATION, getMemberToken())) + .andExpect(status().isNoContent()) + .andDo(print()) + .andDo(document("footprints/delete", + getDocumentRequest(), + resource(ResourceSnippetParameters.builder() + .tag("Footprint API") + .summary("발자국 삭제 API") + .requestHeaders( + headerWithName(AUTHORIZATION).description("로그인한 회원의 accessToken") + ) + .requestSchema(Schema.schema("updateWalkStatusManualResponse")) + .build() + ) + )); + } + @Override protected Object controller() { return new FootprintController(footprintCommandService, footprintQueryService); diff --git a/backend/src/test/java/com/happy/friendogly/footprint/controller/FootprintControllerTest.java b/backend/src/test/java/com/happy/friendogly/footprint/controller/FootprintControllerTest.java index 692d99c38..0ae3f20f0 100644 --- a/backend/src/test/java/com/happy/friendogly/footprint/controller/FootprintControllerTest.java +++ b/backend/src/test/java/com/happy/friendogly/footprint/controller/FootprintControllerTest.java @@ -284,7 +284,7 @@ void findMyLatestFootprintTime_MyFootprintDoesNotExist() { @DisplayName("발자국 범위밖에서 안으로 들어오면 산책중으로 상태가 변한다 (200)") @Test - void updateWalkStatus_toOngoing() { + void updateWalkStatusAuto_toOngoing() { footprintRepository.save( new Footprint( member1, @@ -306,7 +306,7 @@ void updateWalkStatus_toOngoing() { .contentType(ContentType.JSON) .header(HttpHeaders.AUTHORIZATION, getMemberAccessToken(member1.getId())) .body(request) - .when().patch("/footprints/walk-status") + .when().patch("/footprints/recent/walk-status/auto") .then().log().all() .statusCode(HttpStatus.OK.value()) .body("data.walkStatus", is(ONGOING.toString())); @@ -314,7 +314,7 @@ void updateWalkStatus_toOngoing() { @DisplayName("발자국 범위안에서 밖으로 나가면 산책후로 상태가 변한다 (200)") @Test - void updateWalkStatus_toAfter() { + void updateWalkStatusAuto_toAfter() { footprintRepository.save( new Footprint( member1, @@ -336,7 +336,31 @@ void updateWalkStatus_toAfter() { .contentType(ContentType.JSON) .header(HttpHeaders.AUTHORIZATION, getMemberAccessToken(member1.getId())) .body(request) - .when().patch("/footprints/walk-status") + .when().patch("/footprints/recent/walk-status/auto") + .then().log().all() + .statusCode(HttpStatus.OK.value()) + .body("data.walkStatus", is(AFTER.toString())); + } + + @DisplayName("발자국 상태를 수동으로 산책후로 변경한다 (200)") + @Test + void updateWalkStatusManual_toAfter() { + footprintRepository.save( + new Footprint( + member1, + new Location(0, 0), + ONGOING, + LocalDateTime.now(), + null, + LocalDateTime.now().minusHours(1), + false + ) + ); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .header(HttpHeaders.AUTHORIZATION, getMemberAccessToken(member1.getId())) + .when().patch("/footprints/recent/walk-status/manual") .then().log().all() .statusCode(HttpStatus.OK.value()) .body("data.walkStatus", is(AFTER.toString())); diff --git a/backend/src/test/java/com/happy/friendogly/footprint/service/FootprintCommandServiceTest.java b/backend/src/test/java/com/happy/friendogly/footprint/service/FootprintCommandServiceTest.java index 024ec5993..50dfa37bf 100644 --- a/backend/src/test/java/com/happy/friendogly/footprint/service/FootprintCommandServiceTest.java +++ b/backend/src/test/java/com/happy/friendogly/footprint/service/FootprintCommandServiceTest.java @@ -10,7 +10,7 @@ import com.happy.friendogly.footprint.domain.Footprint; import com.happy.friendogly.footprint.domain.Location; import com.happy.friendogly.footprint.dto.request.SaveFootprintRequest; -import com.happy.friendogly.footprint.dto.request.UpdateWalkStatusRequest; +import com.happy.friendogly.footprint.dto.request.UpdateWalkStatusAutoRequest; import com.happy.friendogly.member.domain.Member; import java.time.LocalDateTime; import org.junit.jupiter.api.DisplayName; @@ -98,9 +98,9 @@ void save_With_DeleteRecentFootprint() { @Test void updateWalkStatus_fail_nonExistFootprint() { // when - then - assertThatThrownBy(() -> footprintCommandService.updateWalkStatus( + assertThatThrownBy(() -> footprintCommandService.updateWalkStatusAuto( member.getId(), - new UpdateWalkStatusRequest(0, 0) + new UpdateWalkStatusAutoRequest(0, 0) ) ).isExactlyInstanceOf(FriendoglyException.class) .hasMessage("발자국이 존재하지 않습니다."); @@ -113,9 +113,9 @@ void updateWalkStatus_fail_logicalDeletedFootprint() { footprintRepository.save(FOOTPRINT_DELETED()); // when - then - assertThatThrownBy(() -> footprintCommandService.updateWalkStatus( + assertThatThrownBy(() -> footprintCommandService.updateWalkStatusAuto( member.getId(), - new UpdateWalkStatusRequest(0, 0) + new UpdateWalkStatusAutoRequest(0, 0) ) ).isExactlyInstanceOf(FriendoglyException.class) .hasMessage("가장 최근 발자국이 삭제된 상태입니다."); @@ -129,9 +129,9 @@ void updateWalkStatus_beforeToOngoing() { Footprint savedFootprint = footprintRepository.save(FOOTPRINT_STATUS_BEFORE(new Location(0, 0))); // when - footprintCommandService.updateWalkStatus( + footprintCommandService.updateWalkStatusAuto( member.getId(), - new UpdateWalkStatusRequest(0.0, LONGITUDE_WITH_METER_FROM_ZERO(999)) + new UpdateWalkStatusAutoRequest(0.0, LONGITUDE_WITH_METER_FROM_ZERO(999)) ); // then @@ -146,9 +146,9 @@ void updateWalkStatus_beforeNoChange() { Footprint savedFootprint = footprintRepository.save(FOOTPRINT_STATUS_BEFORE(new Location(0, 0))); // when - footprintCommandService.updateWalkStatus( + footprintCommandService.updateWalkStatusAuto( member.getId(), - new UpdateWalkStatusRequest(0.0, LONGITUDE_WITH_METER_FROM_ZERO(1001)) + new UpdateWalkStatusAutoRequest(0.0, LONGITUDE_WITH_METER_FROM_ZERO(1001)) ); // then @@ -163,9 +163,9 @@ void updateWalkStatus_ongoingToAfter() { Footprint savedFootprint = footprintRepository.save(FOOTPRINT_STATUS_ONGOING(new Location(0, 0))); // when - footprintCommandService.updateWalkStatus( + footprintCommandService.updateWalkStatusAuto( member.getId(), - new UpdateWalkStatusRequest(0.0, LONGITUDE_WITH_METER_FROM_ZERO(1001)) + new UpdateWalkStatusAutoRequest(0.0, LONGITUDE_WITH_METER_FROM_ZERO(1001)) ); // then @@ -181,9 +181,9 @@ void updateWalkStatus_ongoingNoChange() { Footprint savedFootprint = footprintRepository.save(FOOTPRINT_STATUS_ONGOING(new Location(0, 0))); // when - footprintCommandService.updateWalkStatus( + footprintCommandService.updateWalkStatusAuto( member.getId(), - new UpdateWalkStatusRequest(0.0, LONGITUDE_WITH_METER_FROM_ZERO(999)) + new UpdateWalkStatusAutoRequest(0.0, LONGITUDE_WITH_METER_FROM_ZERO(999)) ); // then @@ -199,9 +199,9 @@ void updateWalkStatus_ongoingNoChange_inside() { Footprint savedFootprint = footprintRepository.save(FOOTPRINT_STATUS_AFTER(new Location(0, 0))); // when - footprintCommandService.updateWalkStatus( + footprintCommandService.updateWalkStatusAuto( member.getId(), - new UpdateWalkStatusRequest(0.0, LONGITUDE_WITH_METER_FROM_ZERO(999)) + new UpdateWalkStatusAutoRequest(0.0, LONGITUDE_WITH_METER_FROM_ZERO(999)) ); // then @@ -216,12 +216,74 @@ void updateWalkStatus_afterNoChange_outside() { Footprint savedFootprint = footprintRepository.save(FOOTPRINT_STATUS_AFTER(new Location(0, 0))); // when - footprintCommandService.updateWalkStatus( + footprintCommandService.updateWalkStatusAuto( member.getId(), - new UpdateWalkStatusRequest(0.0, LONGITUDE_WITH_METER_FROM_ZERO(1001)) + new UpdateWalkStatusAutoRequest(0.0, LONGITUDE_WITH_METER_FROM_ZERO(1001)) ); // then assertThat(savedFootprint.getWalkStatus()).isEqualTo(AFTER); } + + @DisplayName("발자국을 삭제할 수 있다") + @Transactional + @Test + void delete() { + // given + Footprint savedFootprint = footprintRepository.save( + FOOTPRINT_STATUS_BEFORE(new Location(0, 0)) + ); + + // when + footprintCommandService.delete(member.getId(), savedFootprint.getId()); + + // then + assertThat(savedFootprint.isDeleted()).isTrue(); + } + + + @DisplayName("최근 발자국의 산책 상태를 수동으로 변경할 수 있다.") + @Transactional + @Test + void updateWalkStatusManual() { + // given + Footprint savedFootprint = footprintRepository.save( + FOOTPRINT_STATUS_ONGOING(new Location(0, 0)) + ); + + // when + footprintCommandService.updateWalkStatusManual(member.getId()); + + // then + assertThat(savedFootprint.getWalkStatus()).isEqualTo(AFTER); + } + + @DisplayName("산책전의 발자국은 산책 상태를 변경할 수 없다.") + @Test + void updateWalkStatusManual_fail_before() { + // given + footprintRepository.save( + FOOTPRINT_STATUS_BEFORE(new Location(0, 0)) + ); + + // when - then + assertThatThrownBy(() -> footprintCommandService.updateWalkStatusManual(member.getId())) + .isInstanceOf(FriendoglyException.class) + .hasMessage("산책 중에서 산책 후로의 변경만 가능합니다."); + } + + @DisplayName("산책후의 발자국은 산책 상태를 변경할 수 없다.") + @Transactional + @Test + void updateWalkStatusManual_fail_after() { + // given + footprintRepository.save( + FOOTPRINT_STATUS_AFTER(new Location(0, 0)) + ); + + // when - then + assertThatThrownBy(() -> footprintCommandService.updateWalkStatusManual(member.getId())) + .isInstanceOf(FriendoglyException.class) + .hasMessage("산책 중에서 산책 후로의 변경만 가능합니다."); + } }