From 990176f1aba32f2d142390a40afb728972059bb4 Mon Sep 17 00:00:00 2001 From: Haedeok Song <86779839+ss0ngcode@users.noreply.github.com> Date: Mon, 19 Aug 2024 10:25:38 +0900 Subject: [PATCH] =?UTF-8?q?[FEAT]=20=EC=A7=80=ED=95=98=EC=B2=A0=20?= =?UTF-8?q?=ED=98=BC=EC=9E=A1=EB=8F=84=20=EA=B4=80=EB=A0=A8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EA=B5=AC=ED=98=84=20(#85)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 작업내용 #59 - 지하철 혼잡도 컨트롤러 구현 - 지하철 혼잡도 서비스 리팩토링 - Exception 처리 변경 --- .../controller/SubwayConfusionController.java | 62 +++++++ .../subway/dao/SubwayConfusionEntity.java | 2 +- .../subway/dao/SubwayConfusionRepository.java | 4 + .../subway/dto/SubwayConfusionRespDto.java | 17 +- .../exception/ConfusionNotFoundException.java | 9 ++ .../service/SubwayConfusionService.java | 53 ++++-- .../subway/service/SubwayStationService.java | 3 +- .../service/SubwayConfusionServiceTest.java | 153 ++++++++++++++---- 8 files changed, 245 insertions(+), 58 deletions(-) create mode 100644 server/src/main/java/com/talkka/server/subway/controller/SubwayConfusionController.java create mode 100644 server/src/main/java/com/talkka/server/subway/exception/ConfusionNotFoundException.java diff --git a/server/src/main/java/com/talkka/server/subway/controller/SubwayConfusionController.java b/server/src/main/java/com/talkka/server/subway/controller/SubwayConfusionController.java new file mode 100644 index 00000000..0b2f51cf --- /dev/null +++ b/server/src/main/java/com/talkka/server/subway/controller/SubwayConfusionController.java @@ -0,0 +1,62 @@ +package com.talkka.server.subway.controller; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Repository; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import com.talkka.server.common.exception.InvalidTypeException; +import com.talkka.server.subway.exception.ConfusionNotFoundException; +import com.talkka.server.subway.exception.StationNotFoundException; +import com.talkka.server.subway.service.SubwayConfusionService; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +@RequestMapping("/api/subway/confusion") +public class SubwayConfusionController { + + private final SubwayConfusionService confusionService; + + @GetMapping("/{stationId}") + public ResponseEntity getConfusion( + @PathVariable Long stationId, + @RequestParam String dayType, + @RequestParam String updown, + @RequestParam String timeSlot + ) { + ResponseEntity response; + try { + response = ResponseEntity.ok( + confusionService.getConfusion(stationId, dayType, updown, timeSlot)); + } catch (StationNotFoundException | InvalidTypeException exception) { + response = ResponseEntity.badRequest().body(exception.getMessage()); + } catch (ConfusionNotFoundException exception) { + response = ResponseEntity.status(HttpStatus.NOT_FOUND).body(exception.getMessage()); + } + return response; + } + + @GetMapping("/{stationId}/list") + public ResponseEntity getConfusionList( + @PathVariable Long stationId, + @RequestParam String dayType, + @RequestParam String updown, + @RequestParam(required = false, defaultValue = "0") String startTimeSlot, + @RequestParam(required = false, defaultValue = "47") String endTimeSlot + ) { + ResponseEntity response; + try { + response = ResponseEntity.ok( + confusionService.getConfusionList(stationId, dayType, updown, startTimeSlot, endTimeSlot) + ); + } catch (StationNotFoundException | InvalidTypeException exception) { + response = ResponseEntity.badRequest().body(exception.getMessage()); + } + return response; + } +} diff --git a/server/src/main/java/com/talkka/server/subway/dao/SubwayConfusionEntity.java b/server/src/main/java/com/talkka/server/subway/dao/SubwayConfusionEntity.java index 4fa70745..b8862510 100644 --- a/server/src/main/java/com/talkka/server/subway/dao/SubwayConfusionEntity.java +++ b/server/src/main/java/com/talkka/server/subway/dao/SubwayConfusionEntity.java @@ -61,7 +61,7 @@ public class SubwayConfusionEntity { @Convert(converter = UpdownConverter.class) private Updown updown; - @Column(name = "time_slot", nullable = false, length = 2) + @Column(name = "time_slot", nullable = false, length = 20) @Enumerated(EnumType.STRING) private TimeSlot timeSlot; diff --git a/server/src/main/java/com/talkka/server/subway/dao/SubwayConfusionRepository.java b/server/src/main/java/com/talkka/server/subway/dao/SubwayConfusionRepository.java index 63690d1f..e04faebe 100644 --- a/server/src/main/java/com/talkka/server/subway/dao/SubwayConfusionRepository.java +++ b/server/src/main/java/com/talkka/server/subway/dao/SubwayConfusionRepository.java @@ -1,5 +1,6 @@ package com.talkka.server.subway.dao; +import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; @@ -13,4 +14,7 @@ public interface SubwayConfusionRepository extends JpaRepository { Optional findBySubwayStationIdAndDayTypeAndUpdownAndTimeSlot( Long stationId, DayType dayType, Updown updown, TimeSlot timeSlot); + + List findBySubwayStationIdAndDayTypeAndUpdownAndTimeSlotBetween( + Long stationId, DayType dayType, Updown updown, TimeSlot startTimeSlot, TimeSlot endTimeSlot); } diff --git a/server/src/main/java/com/talkka/server/subway/dto/SubwayConfusionRespDto.java b/server/src/main/java/com/talkka/server/subway/dto/SubwayConfusionRespDto.java index fc8c8cef..30259e94 100644 --- a/server/src/main/java/com/talkka/server/subway/dto/SubwayConfusionRespDto.java +++ b/server/src/main/java/com/talkka/server/subway/dto/SubwayConfusionRespDto.java @@ -1,26 +1,31 @@ package com.talkka.server.subway.dto; +import com.talkka.server.common.enums.TimeSlot; import com.talkka.server.subway.dao.SubwayConfusionEntity; +import com.talkka.server.subway.enums.DayType; import com.talkka.server.subway.enums.Line; +import com.talkka.server.subway.enums.Updown; import lombok.Builder; @Builder public record SubwayConfusionRespDto( Long stationId, + String stationName, Line line, - String dayType, - String updown, - String timeSlot, + DayType dayType, + Updown updown, + TimeSlot timeSlot, Double confusion ) { public static SubwayConfusionRespDto of(SubwayConfusionEntity subwayConfusionEntity) { return new SubwayConfusionRespDto( subwayConfusionEntity.getId(), + subwayConfusionEntity.getStationName(), subwayConfusionEntity.getLine(), - subwayConfusionEntity.getDayType().getCode(), - subwayConfusionEntity.getUpdown().getCode(), - subwayConfusionEntity.getTimeSlot().toString(), + subwayConfusionEntity.getDayType(), + subwayConfusionEntity.getUpdown(), + subwayConfusionEntity.getTimeSlot(), subwayConfusionEntity.getConfusion() ); } diff --git a/server/src/main/java/com/talkka/server/subway/exception/ConfusionNotFoundException.java b/server/src/main/java/com/talkka/server/subway/exception/ConfusionNotFoundException.java new file mode 100644 index 00000000..8142fff8 --- /dev/null +++ b/server/src/main/java/com/talkka/server/subway/exception/ConfusionNotFoundException.java @@ -0,0 +1,9 @@ +package com.talkka.server.subway.exception; + +public class ConfusionNotFoundException extends RuntimeException { + private static final String MESSAGE = "조회 가능한 혼잡도 정보가 없습니다."; + + public ConfusionNotFoundException() { + super(MESSAGE); + } +} diff --git a/server/src/main/java/com/talkka/server/subway/service/SubwayConfusionService.java b/server/src/main/java/com/talkka/server/subway/service/SubwayConfusionService.java index 1aadbc31..36689023 100644 --- a/server/src/main/java/com/talkka/server/subway/service/SubwayConfusionService.java +++ b/server/src/main/java/com/talkka/server/subway/service/SubwayConfusionService.java @@ -1,16 +1,19 @@ package com.talkka.server.subway.service; +import java.util.List; + import org.springframework.stereotype.Service; import com.talkka.server.common.enums.TimeSlot; -import com.talkka.server.common.exception.http.NotFoundException; -import com.talkka.server.common.util.EnumCodeConverterUtils; +import com.talkka.server.common.exception.InvalidTypeException; import com.talkka.server.subway.dao.SubwayConfusionEntity; import com.talkka.server.subway.dao.SubwayConfusionRepository; import com.talkka.server.subway.dao.SubwayStationRepository; import com.talkka.server.subway.dto.SubwayConfusionRespDto; import com.talkka.server.subway.enums.DayType; import com.talkka.server.subway.enums.Updown; +import com.talkka.server.subway.exception.ConfusionNotFoundException; +import com.talkka.server.subway.exception.StationNotFoundException; import lombok.RequiredArgsConstructor; @@ -19,24 +22,46 @@ public class SubwayConfusionService { private final SubwayConfusionRepository confusionRepository; - private final SubwayStationRepository stationRepository; // TODO 추후 통계쪽으로 넘길지 논의 필요 - public SubwayConfusionRespDto getSubwayConfusion( - Long stationId, String dayTypeCode, String updownCode, String timeSlot - ) { - if (!stationRepository.existsById(stationId)) { - throw new NotFoundException("존재하지 않는 지하철 역입니다."); - } + public SubwayConfusionRespDto getConfusion( + Long stationId, String dayType, String updown, String timeSlot + ) throws StationNotFoundException, ConfusionNotFoundException, InvalidTypeException { + isExisted(stationId); - SubwayConfusionEntity optionalConfusionEntity = confusionRepository.findBySubwayStationIdAndDayTypeAndUpdownAndTimeSlot( + SubwayConfusionEntity entity = confusionRepository.findBySubwayStationIdAndDayTypeAndUpdownAndTimeSlot( stationId, - EnumCodeConverterUtils.fromCode(DayType.class, dayTypeCode), - EnumCodeConverterUtils.fromCode(Updown.class, updownCode), + DayType.valueOfEnumString(dayType), + Updown.valueOfEnumString(updown), TimeSlot.valueOfEnumString(timeSlot) - ).orElseThrow(() -> new NotFoundException("조회 가능한 혼잡도 정보가 없습니다.")); + ).orElseThrow(ConfusionNotFoundException::new); + + return SubwayConfusionRespDto.of(entity); + } + + public List getConfusionList( + Long stationId, String dayType, String updown, String startTimeSlot, String endTimeSlot + ) throws StationNotFoundException, InvalidTypeException { + isExisted(stationId); - return SubwayConfusionRespDto.of(optionalConfusionEntity); + List entityList = confusionRepository.findBySubwayStationIdAndDayTypeAndUpdownAndTimeSlotBetween( + stationId, + DayType.valueOfEnumString(dayType), + Updown.valueOfEnumString(updown), + TimeSlot.valueOfEnumString(startTimeSlot), + TimeSlot.valueOfEnumString(endTimeSlot) + ); + + return entityList.stream() + .map(SubwayConfusionRespDto::of) + .toList(); } + + private void isExisted(Long stationId) { + if (!stationRepository.existsById(stationId)) { + throw new StationNotFoundException(stationId); + } + } + } diff --git a/server/src/main/java/com/talkka/server/subway/service/SubwayStationService.java b/server/src/main/java/com/talkka/server/subway/service/SubwayStationService.java index dae82b55..c01bb624 100644 --- a/server/src/main/java/com/talkka/server/subway/service/SubwayStationService.java +++ b/server/src/main/java/com/talkka/server/subway/service/SubwayStationService.java @@ -10,7 +10,6 @@ import com.talkka.server.subway.dto.SubwayStationRespDto; import com.talkka.server.subway.exception.StationAlreadyExistsException; import com.talkka.server.subway.exception.StationNotFoundException; -import com.talkka.server.subway.exception.enums.InvalidLineEnumException; import lombok.RequiredArgsConstructor; @@ -40,7 +39,7 @@ public List getStationList() { } public SubwayStationRespDto createStation(SubwayStationDto stationDto) - throws StationAlreadyExistsException, InvalidLineEnumException { + throws StationAlreadyExistsException { if (stationRepository.existsByStationCode(stationDto.stationCode())) { throw new StationAlreadyExistsException(stationDto.stationCode()); } diff --git a/server/src/test/java/com/talkka/server/subway/service/SubwayConfusionServiceTest.java b/server/src/test/java/com/talkka/server/subway/service/SubwayConfusionServiceTest.java index 015403d2..9b014bbc 100644 --- a/server/src/test/java/com/talkka/server/subway/service/SubwayConfusionServiceTest.java +++ b/server/src/test/java/com/talkka/server/subway/service/SubwayConfusionServiceTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.Assertions.*; import static org.mockito.BDDMockito.*; +import java.util.List; import java.util.Optional; import org.junit.jupiter.api.DisplayName; @@ -14,15 +15,16 @@ import org.mockito.junit.jupiter.MockitoExtension; import com.talkka.server.common.enums.TimeSlot; -import com.talkka.server.common.exception.http.NotFoundException; -import com.talkka.server.common.util.EnumCodeConverterUtils; import com.talkka.server.subway.dao.SubwayConfusionEntity; import com.talkka.server.subway.dao.SubwayConfusionRepository; import com.talkka.server.subway.dao.SubwayStationEntity; import com.talkka.server.subway.dao.SubwayStationRepository; import com.talkka.server.subway.dto.SubwayConfusionRespDto; import com.talkka.server.subway.enums.DayType; +import com.talkka.server.subway.enums.Line; import com.talkka.server.subway.enums.Updown; +import com.talkka.server.subway.exception.ConfusionNotFoundException; +import com.talkka.server.subway.exception.StationNotFoundException; @ExtendWith(MockitoExtension.class) public class SubwayConfusionServiceTest { @@ -35,74 +37,155 @@ public class SubwayConfusionServiceTest { @Mock private SubwayStationRepository stationRepository; - @Nested - @DisplayName("getSubwayConfusion 메서드 테스트") - public class getSubwayConfusion { + private final Long stationId = 1L; + private final String dayType = DayType.DAY.toString(); + private final String updown = Updown.UP.toString(); + + private SubwayStationEntity getStationEntity(Long stationId) { + return SubwayStationEntity.builder() + .id(stationId) + .stationCode("0150") + .stationName("서울역") + .line(Line.LINE_ONE) + .build(); + } - Long stationId = 1L; - String dayTypeCode = DayType.DAY.getCode(); - String updownCode = Updown.UP.getCode(); - String timeSlotCode = TimeSlot.T_11_00.getCode(); + private SubwayConfusionEntity getConfusionEntity(Long stationId) { + return SubwayConfusionEntity.builder() + .id(1L) + .subwayStation(getStationEntity(stationId)) + .stationCode("0150") + .stationName("서울역") + .line(Line.LINE_ONE) + .dayType(DayType.DAY) + .updown(Updown.UP) + .timeSlot(TimeSlot.T_00_00) + .confusion(100.0) + .build(); + } - DayType dayType = EnumCodeConverterUtils.fromCode(DayType.class, dayTypeCode); - Updown updown = EnumCodeConverterUtils.fromCode(Updown.class, updownCode); - TimeSlot timeSlot = EnumCodeConverterUtils.fromCode(TimeSlot.class, timeSlotCode); + @Nested + @DisplayName("getConfusion 메서드 테스트") + public class getConfusion { + private final String timeSlot = TimeSlot.T_11_00.toString(); @Test void 사용자가_원하는_지하철_역의_ID와_요일과_방향과_시간대를_받아_혼잡도를_반환한다() { //given - SubwayStationEntity subwayStationEntity = SubwayStationEntity.builder() - .id(stationId) - .build(); - - SubwayConfusionEntity subwayConfusionEntity = SubwayConfusionEntity.builder() - .subwayStation(subwayStationEntity) - .dayType(dayType) - .updown(updown) - .timeSlot(timeSlot) - .confusion(100.0) - .build(); + SubwayConfusionEntity entity = getConfusionEntity(stationId); given(stationRepository.existsById(stationId)).willReturn(true); given(confusionRepository.findBySubwayStationIdAndDayTypeAndUpdownAndTimeSlot( - stationId, dayType, updown, timeSlot)).willReturn(Optional.of(subwayConfusionEntity)); + stationId, + DayType.valueOfEnumString(dayType), + Updown.valueOfEnumString(updown), + TimeSlot.valueOfEnumString(timeSlot)) + ).willReturn(Optional.of(entity)); //when - SubwayConfusionRespDto result = confusionService.getSubwayConfusion( - stationId, dayTypeCode, updownCode, timeSlotCode); + SubwayConfusionRespDto result = confusionService.getConfusion( + stationId, dayType, updown, timeSlot); //then - assertThat(result).isEqualTo(SubwayConfusionRespDto.of(subwayConfusionEntity)); + verify(stationRepository, times(1)).existsById(stationId); + verify(confusionRepository, times(1)) + .findBySubwayStationIdAndDayTypeAndUpdownAndTimeSlot( + stationId, + DayType.valueOfEnumString(dayType), + Updown.valueOfEnumString(updown), + TimeSlot.valueOfEnumString(timeSlot) + ); + assertThat(result).isEqualTo(SubwayConfusionRespDto.of(entity)); } @Test void 존재하지_않는_지하철역_ID일_경우_Exception을_throw한다() { //given - Class exceptionClass = NotFoundException.class; // 추후 변경될 가능성이 있어, 변수로 따로 지정함 given(stationRepository.existsById(stationId)).willReturn(false); //when //then assertThatThrownBy( - () -> confusionService.getSubwayConfusion(stationId, dayTypeCode, updownCode, timeSlotCode)) - .isInstanceOf(exceptionClass) - .hasMessage("존재하지 않는 지하철 역입니다."); + () -> confusionService.getConfusion(stationId, dayType, updown, timeSlot)) + .isInstanceOf(StationNotFoundException.class) + .hasMessage("존재하지 않는 지하철 역입니다. StationId: " + stationId); } @Test void 혼잡도가_조회되지_않는_경우_Exception을_throw한다() { //given - Class exceptionClass = NotFoundException.class; // 추후 변경될 가능성이 있어, 변수로 따로 지정함 given(stationRepository.existsById(stationId)).willReturn(true); given(confusionRepository.findBySubwayStationIdAndDayTypeAndUpdownAndTimeSlot( - stationId, dayType, updown, timeSlot)).willReturn(Optional.empty()); + stationId, + DayType.valueOfEnumString(dayType), + Updown.valueOfEnumString(updown), + TimeSlot.valueOfEnumString(timeSlot))) + .willReturn(Optional.empty()); //when //then assertThatThrownBy( - () -> confusionService.getSubwayConfusion(stationId, dayTypeCode, updownCode, timeSlotCode)) - .isInstanceOf(exceptionClass) + () -> confusionService.getConfusion(stationId, dayType, updown, timeSlot)) + .isInstanceOf(ConfusionNotFoundException.class) .hasMessage("조회 가능한 혼잡도 정보가 없습니다."); } } + + @Nested + @DisplayName("getConfusionList 메서드 테스트") + public class getConfusionList { + private final String startTimeSlotCode = TimeSlot.T_08_00.toString(); + private final String endTimeSlotCode = TimeSlot.T_09_00.toString(); + + @Test + void 사용자가_원하는_지하철_역의_ID와_요일과_방향과_시간대들을_받아_혼잡도들을_반환한다() { + //given + List entityList = List.of( + getConfusionEntity(stationId), + getConfusionEntity(stationId) + ); + + List dtoList = entityList.stream() + .map(SubwayConfusionRespDto::of) + .toList(); + + given(stationRepository.existsById(stationId)).willReturn(true); + given(confusionRepository.findBySubwayStationIdAndDayTypeAndUpdownAndTimeSlotBetween( + stationId, + DayType.valueOfEnumString(dayType), + Updown.valueOfEnumString(updown), + TimeSlot.valueOfEnumString(startTimeSlotCode), + TimeSlot.valueOfEnumString(endTimeSlotCode)) + ).willReturn(entityList); + + //when + List result = confusionService.getConfusionList( + stationId, dayType, updown, startTimeSlotCode, endTimeSlotCode); + + //then + verify(stationRepository, times(1)).existsById(stationId); + verify(confusionRepository, times(1)) + .findBySubwayStationIdAndDayTypeAndUpdownAndTimeSlotBetween( + stationId, + DayType.valueOfEnumString(dayType), + Updown.valueOfEnumString(updown), + TimeSlot.valueOfEnumString(startTimeSlotCode), + TimeSlot.valueOfEnumString(endTimeSlotCode) + ); + assertThat(result).isEqualTo(dtoList); + } + + @Test + void 존재하지_않는_지하철역_ID일_경우_Exception을_throw한다() { + //given + given(stationRepository.existsById(stationId)).willReturn(false); + + //when + //then + assertThatThrownBy( + () -> confusionService.getConfusionList(stationId, dayType, updown, startTimeSlotCode, endTimeSlotCode)) + .isInstanceOf(StationNotFoundException.class) + .hasMessage("존재하지 않는 지하철 역입니다. StationId: " + stationId); + } + } }