Skip to content

Commit bc5c340

Browse files
authored
refactor: 대중교통 경로 탐색 시 지하철 상/하행 판별 로직 리팩토링리뷰 반영 (#145)
* refactor: 대중교통 경로 탐색 시 지하철 상/하행 판별 로직 리팩토링리뷰 반영 * refactor: 리뷰 반영
1 parent bdceacd commit bc5c340

6 files changed

Lines changed: 202 additions & 80 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,19 @@ jobs:
1212
- name: Checkout code
1313
uses: actions/checkout@v4 # 깃허브 서버로 내 소스 코드를 가져옴
1414

15+
- name: Set up JDK 21
16+
uses: actions/setup-java@v4
17+
with:
18+
java-version: '21'
19+
distribution: 'temurin'
20+
cache: 'gradle'
21+
22+
- name: Grant execute permission for gradlew
23+
run: chmod +x gradlew
24+
25+
- name: Run Tests with Gradle
26+
run: ./gradlew test
27+
1528
- name: Login to Docker Hub
1629
uses: docker/login-action@v3 # 도커 허브 접속 시도
1730
with:

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ build/
44
!gradle/wrapper/gradle-wrapper.jar
55
!**/src/main/**/build/
66
!**/src/test/**/build/
7+
.gemini
78

89
### STS ###
910
.apt_generated

src/main/java/com/example/pace/domain/schedule/dto/response/info/TransitRouteDetailInfoResDTO.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
@Builder
1818
@Getter
19-
@Setter
2019
@AllArgsConstructor
2120
public class TransitRouteDetailInfoResDTO {
2221

@@ -41,9 +40,12 @@ public class TransitRouteDetailInfoResDTO {
4140

4241

4342
private String headsign;
44-
private List<String> stationPath;
4543

44+
@Setter
45+
private List<String> stationPath;
46+
@Setter
4647
private String upNext; // 상행 다음 정류장
48+
@Setter
4749
private String downNext; // 하행 다음정류장
4850

4951
}

src/main/java/com/example/pace/domain/schedule/service/command/RouteCommandService.java

Lines changed: 32 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -99,45 +99,9 @@ public RouteListResDTO searchRoute(RouteSaveReqDto.CreateRouteDTO request) {
9999

100100
}
101101

102-
103-
public boolean isUpAndDown(SubwayArrivalResDTO.SubwayArrivalInfoDTO arrival, String start, String end,
104-
String lineName) {
105-
106-
// 출발지부터 목적지까지의 경로
107-
List<SubwayStationResDTO> path = subwayNetworkService.getStationsBetween(lineName, start, end);
108-
109-
// 출발지 역이 목적지 역인 경우 그냥 모든 열차를 보여줌
110-
// 추후 상의를 통하여 어케할지 정하기
111-
if (path.size() == 1) {
112-
return true;
113-
}
114-
115-
// 경로가 없을 경우에는 그냥 열차 정보를 주지 않음 -> 이것도 얘기를 함 해보기
116-
if (path.isEmpty()) {
117-
return false;
118-
}
119-
120-
SubwayStationResDTO startStation = path.getFirst();
121-
String secondStationName = path.get(1).getStationName();
122-
123-
// 다음역이 포함되어 있다면 하행으로 판별
124-
boolean isNextDirection = startStation.getNextStations().contains(secondStationName);
125-
126-
String upOrDown = arrival.getUpdnLine();
127-
128-
// 2호선만 외선/내선 (이거아직안함)
129-
if (lineName.equals("2호선")) {
130-
return isNextDirection ? upOrDown.equals("외선") : upOrDown.equals("내선");
131-
} else {
132-
return isNextDirection ? upOrDown.equals("하행") : upOrDown.equals("상행");
133-
}
134-
}
135-
136102
private void enrichTransitPath(RouteListResDTO result) {
137103
for (RouteApiResDto route : result.getRouteApiResDtoList()) {
138-
139104
for (RouteDetailInfoResDTO step : route.getRouteDetailInfoResDTOList()) {
140-
141105
if (step.getTransitDetail() == null) {
142106
continue;
143107
}
@@ -169,56 +133,47 @@ private void enrichTransitPath(RouteListResDTO result) {
169133

170134
// SUBWAY 처리
171135
if (transit.getTransitType() == TransitType.SUBWAY) {
172-
List<String> stationPath = subwayNetworkService.getStationsBetween(lineName, start, end)
173-
.stream()
174-
.map(SubwayStationResDTO::getStationName)
175-
.toList();
176-
transit.setStationPath(stationPath);
177-
178-
SubwayArrivalReqDTO.SubwayArrivalDTO req =
179-
new SubwayArrivalReqDTO.SubwayArrivalDTO(start, end, lineName);
136+
try {
137+
List<SubwayStationResDTO> path = subwayNetworkService.getStationsBetween(lineName, start,
138+
end);
180139

181-
List<SubwayArrivalResDTO.SubwayArrivalInfoDTO> arrivals =
182-
subwayApiQueryService.getLiveSubwayStation(req);
140+
List<String> stationPath = path.stream()
141+
.map(SubwayStationResDTO::getStationName)
142+
.toList();
183143

184-
SubwayArrivalResDTO.SubwayArrivalInfoDTO arrival =
185-
arrivals.isEmpty() ? null : arrivals.getFirst();
186-
// 이거 0번째 리스트 가져와도 아무상관이 없는지...
144+
transit.setStationPath(stationPath);
187145

188-
if (arrival == null) {
189-
// 실시간 정보 없을 때 기본 처리
146+
if (path.size() > 1) {
147+
SubwayStationResDTO startStation = path.getFirst();
148+
String secondStationName = path.get(1).getStationName();
149+
150+
// 해당 출발역의 하행(nextStations) 리스트에 다음역이 있다면 하행으로 판별
151+
boolean isDownbound = startStation.getNextStations().contains(secondStationName);
152+
153+
// 하행이면
154+
if (isDownbound) {
155+
String next = startStation.getNextStations().getFirst();
156+
transit.setDownNext("하행 " + next);
157+
} else { // 상행이면
158+
String prev = startStation.getPrevStations().isEmpty() ? "종점"
159+
: startStation.getPrevStations().getFirst();
160+
transit.setUpNext("상행 " + prev);
161+
}
162+
} else {
163+
// 역이 1개이거나 경로가 없을 경우
164+
transit.setUpNext("정보 없음");
165+
transit.setDownNext("정보 없음");
166+
}
167+
} catch (Exception e) {
168+
log.warn("지하철 상세 경로 및 방향을 찾을 수 없습니다: {} ({} -> {})\ne: {}", lineName, start, end,
169+
e.getMessage());
170+
transit.setStationPath(Collections.emptyList());
190171
transit.setUpNext("정보 없음");
191172
transit.setDownNext("정보 없음");
192-
return;
193-
}
194-
195-
boolean isUp = isUpAndDown(arrival, start, end, lineName);
196-
SubwayStationResDTO startStation =
197-
subwayNetworkService.getStationsBetween(lineName, start, end).get(0);
198-
199-
// T= 상행 F= 하행
200-
if (isUp) {
201-
String prev = startStation.getPrevStations().isEmpty()
202-
? null
203-
: startStation.getPrevStations().get(0);
204-
205-
transit.setDownNext("하행 " + prev);
206-
} else {
207-
String next = startStation.getNextStations().isEmpty()
208-
? null
209-
: startStation.getNextStations().get(0);
210-
211-
transit.setUpNext("상행 " + next);
212-
213173
}
214174
}
215-
216175
}
217-
218-
219176
}
220-
221-
222177
}
223178
}
224179

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package com.example.pace.domain.schedule.service.command;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
import static org.mockito.ArgumentMatchers.any;
5+
import static org.mockito.ArgumentMatchers.anyString;
6+
import static org.mockito.BDDMockito.given;
7+
8+
import com.example.pace.domain.schedule.dto.request.DirectionRequestDTO;
9+
import com.example.pace.domain.schedule.dto.request.RouteSaveReqDto;
10+
import com.example.pace.domain.schedule.dto.response.RouteApiResDto;
11+
import com.example.pace.domain.schedule.dto.response.RouteListResDTO;
12+
import com.example.pace.domain.schedule.dto.response.info.TransitRouteDetailInfoResDTO;
13+
import com.example.pace.domain.schedule.enums.TransitType;
14+
import com.example.pace.domain.schedule.infrastructure.GoogleDirectionApiClient;
15+
import com.example.pace.domain.schedule.infrastructure.dto.GoogleDirectionApiResponse;
16+
import com.example.pace.domain.transit.dto.response.SubwayStationResDTO;
17+
import com.example.pace.domain.transit.service.SubwayNetworkService;
18+
import java.math.BigDecimal;
19+
import java.util.List;
20+
import org.junit.jupiter.api.DisplayName;
21+
import org.junit.jupiter.api.Test;
22+
import org.junit.jupiter.api.extension.ExtendWith;
23+
import org.mockito.InjectMocks;
24+
import org.mockito.Mock;
25+
import org.mockito.junit.jupiter.MockitoExtension;
26+
import org.springframework.test.util.ReflectionTestUtils;
27+
28+
@ExtendWith(MockitoExtension.class)
29+
class RouteCommandServiceUnitTest {
30+
31+
@InjectMocks
32+
private RouteCommandService routeCommandService;
33+
34+
@Mock
35+
private GoogleDirectionApiClient googleDirectionApiClient;
36+
37+
@Mock
38+
private SubwayNetworkService subwayNetworkService;
39+
40+
@Test
41+
@DisplayName("지하철 하행 경로 탐색 시 downNext 필드에 하행 방향이 세팅된다.")
42+
void searchRoute_Subway_Downbound_Success() {
43+
// given: 하행 경로 조건 세팅
44+
RouteSaveReqDto.CreateRouteDTO request = new RouteSaveReqDto.CreateRouteDTO(
45+
BigDecimal.valueOf(37.1), BigDecimal.valueOf(127.1),
46+
BigDecimal.valueOf(37.2), BigDecimal.valueOf(127.2),
47+
null, null, TransitType.SUBWAY, null
48+
);
49+
50+
GoogleDirectionApiResponse googleRes = createMockGoogleResponse("1호선", "시청", "서울역");
51+
given(googleDirectionApiClient.getDirections(any(DirectionRequestDTO.class)))
52+
.willReturn(googleRes);
53+
54+
// 하행 조건: 출발역의 nextStations 리스트에 도착역 포함
55+
SubwayStationResDTO startStation = new SubwayStationResDTO();
56+
ReflectionTestUtils.setField(startStation, "stationName", "시청");
57+
ReflectionTestUtils.setField(startStation, "nextStations", List.of("서울역"));
58+
ReflectionTestUtils.setField(startStation, "prevStations", List.of("종각"));
59+
60+
SubwayStationResDTO secondStation = new SubwayStationResDTO();
61+
ReflectionTestUtils.setField(secondStation, "stationName", "서울역");
62+
63+
given(subwayNetworkService.getStationsBetween(anyString(), anyString(), anyString()))
64+
.willReturn(List.of(startStation, secondStation));
65+
66+
// when: 서비스 메서드 실행
67+
RouteListResDTO result = routeCommandService.searchRoute(request);
68+
69+
// then: 결과 검증
70+
assertThat(result.getRouteApiResDtoList()).hasSize(1);
71+
RouteApiResDto route = result.getRouteApiResDtoList().getFirst();
72+
73+
TransitRouteDetailInfoResDTO transitDetail = route.getRouteDetailInfoResDTOList().getFirst().getTransitDetail();
74+
75+
assertThat(transitDetail.getDownNext()).isEqualTo("하행 서울역");
76+
assertThat(transitDetail.getUpNext()).isNull();
77+
assertThat(transitDetail.getStationPath()).containsExactly("시청", "서울역");
78+
}
79+
80+
@Test
81+
@DisplayName("지하철 상행 경로 탐색 시 upNext 필드에 상행 방향이 세팅된다.")
82+
void searchRoute_Subway_Upbound_Success() {
83+
// given: 상행 경로 조건 세팅
84+
RouteSaveReqDto.CreateRouteDTO request = new RouteSaveReqDto.CreateRouteDTO(
85+
BigDecimal.valueOf(37.1), BigDecimal.valueOf(127.1),
86+
BigDecimal.valueOf(37.2), BigDecimal.valueOf(127.2),
87+
null, null, TransitType.SUBWAY, null
88+
);
89+
90+
GoogleDirectionApiResponse googleRes = createMockGoogleResponse("1호선", "서울역", "시청");
91+
given(googleDirectionApiClient.getDirections(any(DirectionRequestDTO.class)))
92+
.willReturn(googleRes);
93+
94+
// 상행 조건: 출발역의 prevStations 리스트에 도착역 포함
95+
SubwayStationResDTO startStation = new SubwayStationResDTO();
96+
ReflectionTestUtils.setField(startStation, "stationName", "서울역");
97+
ReflectionTestUtils.setField(startStation, "nextStations", List.of("남영"));
98+
ReflectionTestUtils.setField(startStation, "prevStations", List.of("시청"));
99+
100+
SubwayStationResDTO secondStation = new SubwayStationResDTO();
101+
ReflectionTestUtils.setField(secondStation, "stationName", "시청");
102+
103+
given(subwayNetworkService.getStationsBetween(anyString(), anyString(), anyString()))
104+
.willReturn(List.of(startStation, secondStation));
105+
106+
// when: 서비스 메서드 실행
107+
RouteListResDTO result = routeCommandService.searchRoute(request);
108+
109+
// then: 결과 검증
110+
TransitRouteDetailInfoResDTO transitDetail = result.getRouteApiResDtoList().getFirst()
111+
.getRouteDetailInfoResDTOList().getFirst().getTransitDetail();
112+
113+
assertThat(transitDetail.getUpNext()).isEqualTo("상행 시청");
114+
assertThat(transitDetail.getDownNext()).isNull();
115+
}
116+
117+
private GoogleDirectionApiResponse createMockGoogleResponse(String lineName, String startStop, String endStop) {
118+
GoogleDirectionApiResponse.EncodedLine line = new GoogleDirectionApiResponse.EncodedLine();
119+
ReflectionTestUtils.setField(line, "name", lineName);
120+
121+
GoogleDirectionApiResponse.Vehicle vehicle = new GoogleDirectionApiResponse.Vehicle();
122+
ReflectionTestUtils.setField(vehicle, "type", "SUBWAY");
123+
ReflectionTestUtils.setField(line, "vehicle", vehicle);
124+
125+
GoogleDirectionApiResponse.TransitDetails transitDetails = new GoogleDirectionApiResponse.TransitDetails();
126+
ReflectionTestUtils.setField(transitDetails, "encodedLine", line);
127+
128+
GoogleDirectionApiResponse.Step step = new GoogleDirectionApiResponse.Step();
129+
ReflectionTestUtils.setField(step, "travelMode", "TRANSIT");
130+
ReflectionTestUtils.setField(step, "transitDetails", transitDetails);
131+
132+
GoogleDirectionApiResponse.Leg leg = new GoogleDirectionApiResponse.Leg();
133+
ReflectionTestUtils.setField(leg, "steps", List.of(step));
134+
135+
GoogleDirectionApiResponse.Route route = new GoogleDirectionApiResponse.Route();
136+
ReflectionTestUtils.setField(route, "legs", List.of(leg));
137+
138+
GoogleDirectionApiResponse response = new GoogleDirectionApiResponse();
139+
ReflectionTestUtils.setField(response, "routes", List.of(route));
140+
141+
GoogleDirectionApiResponse.DepartureStop departureStop = new GoogleDirectionApiResponse.DepartureStop();
142+
ReflectionTestUtils.setField(departureStop, "encodedName", startStop);
143+
ReflectionTestUtils.setField(transitDetails, "departureStop", departureStop);
144+
145+
GoogleDirectionApiResponse.ArrivalStop arrivalStop = new GoogleDirectionApiResponse.ArrivalStop();
146+
ReflectionTestUtils.setField(arrivalStop, "encodedName", endStop);
147+
ReflectionTestUtils.setField(transitDetails, "arrivalStop", arrivalStop);
148+
149+
return response;
150+
}
151+
}

src/test/java/com/example/pace/domain/transit/service/BusNetworkServiceTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
import static org.assertj.core.api.Assertions.assertThat;
1414

15-
// @Disabled
15+
@Disabled
1616
@SpringBootTest
1717
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) // H2로 교체하지 않고 실제 DB 사용
1818
class BusNetworkServiceTest {

0 commit comments

Comments
 (0)