diff --git a/src/main/java/nextstep/favorite/application/service/FavoriteService.java b/src/main/java/nextstep/favorite/application/service/FavoriteService.java index 9cdafcd49..89f0c41cc 100644 --- a/src/main/java/nextstep/favorite/application/service/FavoriteService.java +++ b/src/main/java/nextstep/favorite/application/service/FavoriteService.java @@ -14,8 +14,8 @@ import nextstep.member.domain.MemberDetailCustom; import nextstep.member.domain.MemberRepository; import nextstep.subway.application.service.StationService; -import nextstep.subway.domain.entity.PathFinder; import nextstep.subway.domain.entity.Station; +import nextstep.subway.domain.entity.SubwayMap; import nextstep.subway.domain.repository.LineRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -36,8 +36,8 @@ public Long createFavorite(MemberDetailCustom memberDetailCustom, FavoriteReques Station source = stationService.getStationById(request.getSource()); Station target = stationService.getStationById(request.getTarget()); Member member = getMember(memberDetailCustom); - PathFinder pathFinder = new PathFinder(lineRepository.findAll()); - if (!pathFinder.isValidPath(source, target)) { + SubwayMap subwayMap = new SubwayMap(lineRepository.findAll()); + if (!subwayMap.isValidPath(source, target)) { throw new BadRequestException("invalid favorite info"); } Favorite favorite = favoriteRepository.save(Favorite.of(member.getId(), source, target)); diff --git a/src/main/java/nextstep/subway/application/dto/request/AddSectionRequest.java b/src/main/java/nextstep/subway/application/dto/request/AddSectionRequest.java index dd2b221ca..a76307cfc 100644 --- a/src/main/java/nextstep/subway/application/dto/request/AddSectionRequest.java +++ b/src/main/java/nextstep/subway/application/dto/request/AddSectionRequest.java @@ -7,14 +7,14 @@ public class AddSectionRequest { private final Long upStationId; - private final Long downStationId; - private final Long distance; + private final Long duration; - public AddSectionRequest(Long upStationId, Long downStationId, Long distance) { + public AddSectionRequest(Long upStationId, Long downStationId, Long distance, Long duration) { this.upStationId = upStationId; this.downStationId = downStationId; this.distance = distance; + this.duration = duration; } } diff --git a/src/main/java/nextstep/subway/application/dto/request/LineCreateRequest.java b/src/main/java/nextstep/subway/application/dto/request/LineCreateRequest.java index 3be6e41d6..d9ce3bb59 100644 --- a/src/main/java/nextstep/subway/application/dto/request/LineCreateRequest.java +++ b/src/main/java/nextstep/subway/application/dto/request/LineCreateRequest.java @@ -15,12 +15,15 @@ public class LineCreateRequest { private final Long downStationId; private final Long distance; + private final Long duration; - public LineCreateRequest(String name, String color, Long upStationId, Long downStationId, Long distance) { + public LineCreateRequest(String name, String color, Long upStationId, Long downStationId, Long distance, + Long duration) { this.name = name; this.color = color; this.upStationId = upStationId; this.downStationId = downStationId; this.distance = distance; + this.duration = duration; } } diff --git a/src/main/java/nextstep/subway/application/dto/response/PathResponse.java b/src/main/java/nextstep/subway/application/dto/response/PathResponse.java index 47ede6ff5..2dcf1ca64 100644 --- a/src/main/java/nextstep/subway/application/dto/response/PathResponse.java +++ b/src/main/java/nextstep/subway/application/dto/response/PathResponse.java @@ -1,10 +1,8 @@ package nextstep.subway.application.dto.response; import java.util.List; -import java.util.stream.Collectors; import lombok.Getter; import lombok.NoArgsConstructor; -import nextstep.subway.domain.entity.Path; import nextstep.subway.domain.entity.Station; @NoArgsConstructor @@ -28,14 +26,13 @@ public StationDto(Station station) { private List stations; private long distance; - - public PathResponse(Path shortestPath) { - this.stations = shortestPath.getStations().stream() - .map(StationDto::new) - .collect(Collectors.toList()); - this.distance = (long) shortestPath.getDistance(); - + private long duration; + private int fare; + + public PathResponse(List stations, long distance, long duration, int fare) { + this.stations = stations; + this.distance = distance; + this.duration = duration; + this.fare = fare; } - - } diff --git a/src/main/java/nextstep/subway/application/service/LineService.java b/src/main/java/nextstep/subway/application/service/LineService.java index 0d3badd12..d2f8ed5ff 100644 --- a/src/main/java/nextstep/subway/application/service/LineService.java +++ b/src/main/java/nextstep/subway/application/service/LineService.java @@ -32,7 +32,15 @@ public LineResponse saveLine(LineCreateRequest lineCreateRequest) { lineCreateRequest.getName(), lineCreateRequest.getColor() )); - line.addSection(Section.of(line, upStation, downStation, lineCreateRequest.getDistance())); + line.addSection( + Section.of( + line, + upStation, + downStation, + lineCreateRequest.getDistance(), + lineCreateRequest.getDuration() + ) + ); return new LineResponse(line); } @@ -67,7 +75,13 @@ public LineResponse addSection(Long lineId, AddSectionRequest addSectionRequest) Station upStation = stationService.getStationById(addSectionRequest.getUpStationId()); Station downStation = stationService.getStationById(addSectionRequest.getDownStationId()); line.addSection( - Section.of(line, upStation, downStation, addSectionRequest.getDistance()) + Section.of( + line, + upStation, + downStation, + addSectionRequest.getDistance(), + addSectionRequest.getDuration() + ) ); return new LineResponse(line); } diff --git a/src/main/java/nextstep/subway/application/service/PathService.java b/src/main/java/nextstep/subway/application/service/PathService.java index cf4c6a94f..c76ef2be1 100644 --- a/src/main/java/nextstep/subway/application/service/PathService.java +++ b/src/main/java/nextstep/subway/application/service/PathService.java @@ -6,8 +6,10 @@ import nextstep.subway.application.exception.CanNotFindPathException; import nextstep.subway.domain.entity.Line; import nextstep.subway.domain.entity.Path; -import nextstep.subway.domain.entity.PathFinder; import nextstep.subway.domain.entity.Station; +import nextstep.subway.domain.entity.SubWayFare; +import nextstep.subway.domain.entity.SubwayMap; +import nextstep.subway.domain.enums.PathSearchType; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -19,14 +21,20 @@ public class PathService { private final StationService stationService; private final LineService lineService; - public PathResponse findPath(Long sourceStationId, Long targetStationId) { + public PathResponse findPath(Long sourceStationId, Long targetStationId, PathSearchType type) { Station source = stationService.getStationById(sourceStationId); Station target = stationService.getStationById(targetStationId); List lines = lineService.getLines(); - PathFinder pathFinder = new PathFinder(lines); - Path shortestPath = pathFinder.findShortestPath(source, target) + SubwayMap subwayMap = new SubwayMap(lines); + Path shortestPath = subwayMap.findShortestPath(source, target, type) .orElseThrow(() -> new CanNotFindPathException("Unable to find the shortest path.")); - return new PathResponse(shortestPath); + SubWayFare subWayFare = new SubWayFare(shortestPath); + return new PathResponse( + shortestPath.getStationDtoOfPath(target), + shortestPath.getDistance(), + shortestPath.getDuration(), + subWayFare.calculateFare() + ); } diff --git a/src/main/java/nextstep/subway/domain/entity/DistanceFarePolicy.java b/src/main/java/nextstep/subway/domain/entity/DistanceFarePolicy.java new file mode 100644 index 000000000..243e65dc3 --- /dev/null +++ b/src/main/java/nextstep/subway/domain/entity/DistanceFarePolicy.java @@ -0,0 +1,39 @@ +package nextstep.subway.domain.entity; + +public class DistanceFarePolicy implements FarePolicy { + + + public static final long DEFAULT_DISTANCE = 10L; + public static final int OVER_10KM_DISTANCE = 10; + public static final int OVER_50KM_DISTANCE = 50; + public static final int OVER_10KM_FARE_DISTANCE = 5; + public static final int OVER_50KM_FARE_DISTANCE = 8; + public static final int OVER_10KM_FARE_AMOUNT = 100; + public static final int OVER_50KM_FARE_AMOUNT = 100; + private final long distance; + + public DistanceFarePolicy(long distance) { + this.distance = distance; + } + + @Override + public int getAdditionalFare() { + if (distance > DEFAULT_DISTANCE) { + return calculateOverFare(distance); + } + return 0; + } + + private int calculateOverFare(long distance) { + if (distance > OVER_50KM_DISTANCE) { + return (int) ((Math.ceil((distance - DEFAULT_DISTANCE - 1) / OVER_50KM_FARE_DISTANCE) + 1) + * OVER_50KM_FARE_AMOUNT); + } + if (distance > OVER_10KM_DISTANCE) { + return (int) ((Math.ceil((distance - DEFAULT_DISTANCE - 1) / OVER_10KM_FARE_DISTANCE) + 1) + * OVER_10KM_FARE_AMOUNT); + } + return 0; + } + +} diff --git a/src/main/java/nextstep/subway/domain/entity/FarePolicy.java b/src/main/java/nextstep/subway/domain/entity/FarePolicy.java new file mode 100644 index 000000000..ea91e8db8 --- /dev/null +++ b/src/main/java/nextstep/subway/domain/entity/FarePolicy.java @@ -0,0 +1,6 @@ +package nextstep.subway.domain.entity; + +public interface FarePolicy { + + int getAdditionalFare(); +} diff --git a/src/main/java/nextstep/subway/domain/entity/Path.java b/src/main/java/nextstep/subway/domain/entity/Path.java index fba795c61..3fb85d039 100644 --- a/src/main/java/nextstep/subway/domain/entity/Path.java +++ b/src/main/java/nextstep/subway/domain/entity/Path.java @@ -1,16 +1,44 @@ package nextstep.subway.domain.entity; +import java.util.Collections; import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; import lombok.Getter; +import nextstep.subway.application.dto.response.PathResponse.StationDto; @Getter public class Path { - private List stations; - private double distance; + private final Sections sections; - public Path(List stations, double distance) { - this.stations = stations; - this.distance = distance; + public Path(Sections sections) { + this.sections = sections; + } + + public List getStationDtoOfPath(Station target) { + List stations = this.getStationsOfPath(); + if (!stations.isEmpty()) { + Station sourceStation = stations.get(0); + if (Objects.equals(sourceStation.getId(), target.getId())) { + Collections.reverse(stations); + } + } + return stations.stream() + .map(StationDto::new) + .collect(Collectors.toList()); + } + + public List getStationsOfPath() { + return this.sections.getSortedStationsByUpDirection(true); + } + + + public long getDistance() { + return this.sections.getDistance(); + } + + public long getDuration() { + return this.sections.getDuration(); } } diff --git a/src/main/java/nextstep/subway/domain/entity/PathFinder.java b/src/main/java/nextstep/subway/domain/entity/PathFinder.java deleted file mode 100644 index abfe99b8a..000000000 --- a/src/main/java/nextstep/subway/domain/entity/PathFinder.java +++ /dev/null @@ -1,64 +0,0 @@ -package nextstep.subway.domain.entity; - -import java.util.List; -import java.util.Optional; -import lombok.extern.slf4j.Slf4j; -import org.jgrapht.Graph; -import org.jgrapht.GraphPath; -import org.jgrapht.WeightedGraph; -import org.jgrapht.alg.shortestpath.DijkstraShortestPath; -import org.jgrapht.graph.DefaultWeightedEdge; -import org.jgrapht.graph.SimpleWeightedGraph; - -@Slf4j -public class PathFinder { - - private final List lines; - private final DijkstraShortestPath dijkstraShortestPath; - - public PathFinder(List lines) { - this.lines = lines; - this.dijkstraShortestPath = new DijkstraShortestPath<>(createWeightedGraph()); - } - - public Optional findShortestPath(Station source, Station target) { - if (source == target) { - throw new IllegalArgumentException("Source and target stations are the same"); - } - try { - GraphPath result = dijkstraShortestPath.getPath(source, target); - return Optional.of(new Path(result.getVertexList(), result.getWeight())); - } catch (Exception e) { - return Optional.empty(); - } - } - - public boolean isValidPath(Station source, Station target) { - try { - Optional path = findShortestPath(source, target); - return path.isPresent(); - } catch (IllegalArgumentException e) { - return false; - } - } - - private WeightedGraph createWeightedGraph() { - SimpleWeightedGraph graph = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); - addStationsAsVerticesToGraph(graph); - addSectionsAdSeightedEdgeToGraph(graph); - return graph; - } - - private void addStationsAsVerticesToGraph(Graph graph) { - this.lines.stream() - .flatMap(line -> line.getAllStationsByDistinct().stream()) - .forEach(graph::addVertex); - } - - private void addSectionsAdSeightedEdgeToGraph(WeightedGraph graph) { - this.lines.stream() - .flatMap(line -> line.getSections().getAllSections().stream()) - .forEach(section -> graph.setEdgeWeight( - graph.addEdge(section.getUpStation(), section.getDownStation()), section.getDistance())); - } -} diff --git a/src/main/java/nextstep/subway/domain/entity/Section.java b/src/main/java/nextstep/subway/domain/entity/Section.java index e8523dfba..45f7e28d6 100644 --- a/src/main/java/nextstep/subway/domain/entity/Section.java +++ b/src/main/java/nextstep/subway/domain/entity/Section.java @@ -35,15 +35,18 @@ public class Section implements Comparable
{ private long distance; - public Section(Line line, Station upStation, Station downStation, long distance) { + private long duration; + + public Section(Line line, Station upStation, Station downStation, long distance, long duration) { this.line = line; this.upStation = upStation; this.downStation = downStation; this.distance = distance; + this.duration = duration; } - public static Section of(Line line, Station upStation, Station downStation, long distance) { - return new Section(line, upStation, downStation, distance); + public static Section of(Line line, Station upStation, Station downStation, long distance, long duration) { + return new Section(line, upStation, downStation, distance, duration); } @Override diff --git a/src/main/java/nextstep/subway/domain/entity/Sections.java b/src/main/java/nextstep/subway/domain/entity/Sections.java index 444f4ea0f..3c07161ea 100644 --- a/src/main/java/nextstep/subway/domain/entity/Sections.java +++ b/src/main/java/nextstep/subway/domain/entity/Sections.java @@ -140,4 +140,12 @@ private Optional
getSectionByDownStation(Station station) { public List
getAllSections() { return this.sections; } + + public long getDistance() { + return this.sections.stream().mapToLong(Section::getDistance).sum(); + } + + public long getDuration() { + return this.sections.stream().mapToLong(Section::getDuration).sum(); + } } diff --git a/src/main/java/nextstep/subway/domain/entity/SubWayFare.java b/src/main/java/nextstep/subway/domain/entity/SubWayFare.java new file mode 100644 index 000000000..543f26872 --- /dev/null +++ b/src/main/java/nextstep/subway/domain/entity/SubWayFare.java @@ -0,0 +1,20 @@ +package nextstep.subway.domain.entity; + +import java.util.List; + +public class SubWayFare { + + private static final int DEFAULT_FARE = 1250; + private final List policies; + private int fare; + + public SubWayFare(Path path) { + this.policies = List.of(new DistanceFarePolicy(path.getDistance())); + this.fare = DEFAULT_FARE; + } + + public int calculateFare() { + policies.forEach(policy -> fare += policy.getAdditionalFare()); + return fare; + } +} diff --git a/src/main/java/nextstep/subway/domain/entity/SubwayMap.java b/src/main/java/nextstep/subway/domain/entity/SubwayMap.java new file mode 100644 index 000000000..793b031ef --- /dev/null +++ b/src/main/java/nextstep/subway/domain/entity/SubwayMap.java @@ -0,0 +1,107 @@ +package nextstep.subway.domain.entity; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import nextstep.subway.domain.enums.PathSearchType; +import org.jgrapht.Graph; +import org.jgrapht.GraphPath; +import org.jgrapht.WeightedGraph; +import org.jgrapht.alg.shortestpath.DijkstraShortestPath; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.SimpleWeightedGraph; + +@Slf4j +public class SubwayMap { + + @Getter + static class SectionEdge extends DefaultWeightedEdge { + + private final Section section; + + SectionEdge(Section section) { + this.section = section; + } + } + + private final List lines; + private final DijkstraShortestPath distanceShortestPath; + private final DijkstraShortestPath durationShortestPath; + + public SubwayMap(List lines) { + this.lines = lines; + this.distanceShortestPath = new DijkstraShortestPath<>(createWeightedGraph(PathSearchType.DISTANCE)); + this.durationShortestPath = new DijkstraShortestPath<>(createWeightedGraph(PathSearchType.DURATION)); + } + + public Optional findShortestPath(Station source, Station target, PathSearchType type) { + if (source == target) { + throw new IllegalArgumentException("Source and target stations are the same"); + } + try { + GraphPath result = getShortedPath(source, target, type); + List
sections = result.getEdgeList().stream() + .map(SectionEdge::getSection) + .collect(Collectors.toList()); + return Optional.of(new Path(new Sections(sections))); + } catch (Exception e) { + return Optional.empty(); + } + } + + public boolean isValidPath(Station source, Station target) { + try { + Optional path = findShortestPath(source, target, PathSearchType.DISTANCE); + return path.isPresent(); + } catch (IllegalArgumentException e) { + return false; + } + } + + private GraphPath getShortedPath(Station source, Station target, + PathSearchType type) { + if (type == PathSearchType.DISTANCE) { + return distanceShortestPath.getPath(source, target); + } + if (type == PathSearchType.DURATION) { + return durationShortestPath.getPath(source, target); + } + throw new IllegalStateException("invalid PathSearchType type"); + } + + private WeightedGraph createWeightedGraph(PathSearchType type) { + SimpleWeightedGraph graph = new SimpleWeightedGraph<>(SectionEdge.class); + addStationsAsVerticesToGraph(graph); + addSectionsAsWeightedEdgeToGraph(graph, type); + return graph; + } + + private void addStationsAsVerticesToGraph(Graph graph) { + this.lines.stream() + .flatMap(line -> line.getAllStationsByDistinct().stream()) + .forEach(graph::addVertex); + } + + private void addSectionsAsWeightedEdgeToGraph(WeightedGraph graph, + PathSearchType type) { + this.lines.stream() + .flatMap(line -> line.getSections().getAllSections().stream()) + .forEach(section -> { + SectionEdge sectionEdge = new SectionEdge(section); + graph.addEdge(section.getUpStation(), section.getDownStation(), sectionEdge); + graph.setEdgeWeight(sectionEdge, getSectionEdgeWeight(section, type)); + }); + } + + private long getSectionEdgeWeight(Section section, PathSearchType type) { + if (type == PathSearchType.DISTANCE) { + return section.getDistance(); + } + if (type == PathSearchType.DURATION) { + return section.getDuration(); + } + throw new IllegalStateException("invalid PathSearchType type"); + } +} diff --git a/src/main/java/nextstep/subway/domain/enums/PathSearchType.java b/src/main/java/nextstep/subway/domain/enums/PathSearchType.java new file mode 100644 index 000000000..da5eac454 --- /dev/null +++ b/src/main/java/nextstep/subway/domain/enums/PathSearchType.java @@ -0,0 +1,6 @@ +package nextstep.subway.domain.enums; + +public enum PathSearchType { + DISTANCE, + DURATION +} diff --git a/src/main/java/nextstep/subway/ui/PathController.java b/src/main/java/nextstep/subway/ui/PathController.java index c5d6df00f..de057856d 100644 --- a/src/main/java/nextstep/subway/ui/PathController.java +++ b/src/main/java/nextstep/subway/ui/PathController.java @@ -3,6 +3,7 @@ import lombok.RequiredArgsConstructor; import nextstep.subway.application.dto.response.PathResponse; import nextstep.subway.application.service.PathService; +import nextstep.subway.domain.enums.PathSearchType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -20,9 +21,10 @@ public class PathController { @GetMapping public ResponseEntity findPath( @RequestParam(name = "source") Long sourceStationId, - @RequestParam(name = "target") Long targetStationId + @RequestParam(name = "target") Long targetStationId, + @RequestParam(name = "type") PathSearchType type ) { - return ResponseEntity.ok().body(pathService.findPath(sourceStationId, targetStationId)); + return ResponseEntity.ok().body(pathService.findPath(sourceStationId, targetStationId, type)); } } diff --git a/src/test/java/nextstep/cucumber/AcceptanceContext.java b/src/test/java/nextstep/cucumber/AcceptanceContext.java index 9bbbcf582..1561e01eb 100644 --- a/src/test/java/nextstep/cucumber/AcceptanceContext.java +++ b/src/test/java/nextstep/cucumber/AcceptanceContext.java @@ -13,4 +13,8 @@ public class AcceptanceContext { public Map store = new HashMap<>(); public ExtractableResponse response; + + public T getValueFromStore(String name, Class responseType) { + return responseType.cast(this.store.get(name)); + } } diff --git a/src/test/java/nextstep/cucumber/steps/LineStepDef.java b/src/test/java/nextstep/cucumber/steps/LineStepDef.java index 2b73eb24f..782e523bd 100644 --- a/src/test/java/nextstep/cucumber/steps/LineStepDef.java +++ b/src/test/java/nextstep/cucumber/steps/LineStepDef.java @@ -27,10 +27,11 @@ public LineStepDef() { params.put("name", param.get("name")); params.put("color", param.get("color")); params.put("upStationId", - ((StationResponse) context.store.get(param.get("upStation"))).getId().toString()); + context.getValueFromStore(param.get("upStation"), StationResponse.class).getId().toString()); params.put("downStationId", - ((StationResponse) context.store.get(param.get("downStation"))).getId().toString()); + context.getValueFromStore(param.get("downStation"), StationResponse.class).getId().toString()); params.put("distance", param.get("distance")); + params.put("duration", param.get("duration")); ExtractableResponse response = 지하철_노선_생성_요청(params); context.store.put(params.get("name").toString(), response.as(LineResponse.class)); }); @@ -38,4 +39,5 @@ public LineStepDef() { } + } diff --git a/src/test/java/nextstep/cucumber/steps/PathStepDef.java b/src/test/java/nextstep/cucumber/steps/PathStepDef.java index bf060ac04..cad492acf 100644 --- a/src/test/java/nextstep/cucumber/steps/PathStepDef.java +++ b/src/test/java/nextstep/cucumber/steps/PathStepDef.java @@ -2,7 +2,9 @@ import static nextstep.subway.acceptance.step.PathSteps.지하철_경로_조회_요청; import static nextstep.subway.acceptance.step.PathSteps.지하철역_경로_조회_응답에서_경로_거리_추출; +import static nextstep.subway.acceptance.step.PathSteps.지하철역_경로_조회_응답에서_경로_시간_추출; import static nextstep.subway.acceptance.step.PathSteps.지하철역_경로_조회_응답에서_역_이름_목록_추출; +import static nextstep.subway.acceptance.step.PathSteps.지하철역_경로_조회_응답에서_지하철_요금_추출; import static nextstep.subway.acceptance.step.SectionSteps.지하철_구간_등록_요청; import io.cucumber.datatable.DataTable; import io.cucumber.java8.En; @@ -13,6 +15,7 @@ import nextstep.cucumber.AcceptanceContext; import nextstep.subway.application.dto.response.LineResponse; import nextstep.subway.application.dto.response.StationResponse; +import nextstep.subway.domain.enums.PathSearchType; import org.assertj.core.api.SoftAssertions; import org.springframework.beans.factory.annotation.Autowired; @@ -30,30 +33,55 @@ public PathStepDef() { String lineName = param.get("lineName"); Map params = new HashMap<>(); params.put("upStationId", - ((StationResponse) context.store.get(param.get("upStation"))).getId().toString()); + context.getValueFromStore(param.get("upStation"), StationResponse.class).getId().toString()); params.put("downStationId", - ((StationResponse) context.store.get(param.get("downStation"))).getId().toString()); + context.getValueFromStore(param.get("downStation"), StationResponse.class).getId().toString()); params.put("distance", param.get("distance")); - LineResponse line = (LineResponse) context.store.get(lineName); + params.put("duration", param.get("duration")); + LineResponse line = context.getValueFromStore(lineName, LineResponse.class); 지하철_구간_등록_요청(line.getId(), params); }); }); - When("{string}과 {string}의 경로를 조회하면", (String sourceStationName, String targetStationName) -> { - Long sourceId = ((StationResponse) context.store.get(sourceStationName)).getId(); - Long targetId = ((StationResponse) context.store.get(targetStationName)).getId(); - context.response = 지하철_경로_조회_요청(sourceId, targetId); - }); - - Then("거리가 {long}인 {string}경로가 조회된다", (Long distance, String path) -> { + When("{string}에서 {string}까지의 {string} 기준으로 경로 조회를 요청하면", + (String sourceStationName, String targetStationName, String type) -> { + Long sourceId = ((StationResponse) context.store.get(sourceStationName)).getId(); + Long targetId = ((StationResponse) context.store.get(targetStationName)).getId(); + PathSearchType searchType = getPathSearchType(type); + context.response = 지하철_경로_조회_요청(sourceId, targetId, searchType); + }); + Then("{string} 기준 {string} 경로를 응답", (String type, String path) -> { List stationNames = Arrays.asList(path.split(",")); SoftAssertions.assertSoftly(softAssertions -> { softAssertions.assertThat(지하철역_경로_조회_응답에서_역_이름_목록_추출(context.response)) .containsExactlyElementsOf(stationNames); + }); + }); + + And("총 거리 {long}와 소요 시간 {long}을 함께 응답함", (Long distance, Long duration) -> { + SoftAssertions.assertSoftly(softAssertions -> { softAssertions.assertThat(지하철역_경로_조회_응답에서_경로_거리_추출(context.response)) .isEqualTo(distance); + softAssertions.assertThat(지하철역_경로_조회_응답에서_경로_시간_추출(context.response)) + .isEqualTo(duration); + }); + }); + And("이용 요금 {int}도 함께 응답함", (Integer fare) -> { + SoftAssertions.assertSoftly(softAssertions -> { + softAssertions.assertThat(지하철역_경로_조회_응답에서_지하철_요금_추출(context.response)) + .isEqualTo(fare); }); }); } + private PathSearchType getPathSearchType(String type) { + if (type.equals("최소 시간")) { + return PathSearchType.DURATION; + } + if (type.equals("최소 거리")) { + return PathSearchType.DISTANCE; + } + throw new IllegalArgumentException(String.format("do not match PathSearchType: %s", type)); + } + } diff --git a/src/test/java/nextstep/favorite/acceptance/step/FavoriteAcceptanceTest.java b/src/test/java/nextstep/favorite/acceptance/step/FavoriteAcceptanceTest.java index 0858622b1..4e95c7113 100644 --- a/src/test/java/nextstep/favorite/acceptance/step/FavoriteAcceptanceTest.java +++ b/src/test/java/nextstep/favorite/acceptance/step/FavoriteAcceptanceTest.java @@ -129,11 +129,11 @@ void deleteFavorite() { 양재역_아이디 = 지하철역_응답에서_역_아이디_추출(지하철_역_생성_요청(지하철역_생성_요청_본문("양재역"))); 남부터미널역_아이디 = 지하철역_응답에서_역_아이디_추출(지하철_역_생성_요청(지하철역_생성_요청_본문("남부터미널역"))); - 이호선_아이디 = 지하철_노선_응답에서_노선_아이디_추출(지하철_노선_생성_요청(노선_생성_요청_본문("2호선", "green", 교대역_아이디, 강남역_아이디, 10L))); - 신분당선_아이디 = 지하철_노선_응답에서_노선_아이디_추출(지하철_노선_생성_요청(노선_생성_요청_본문("신분당선", "red", 강남역_아이디, 양재역_아이디, 10L))); - 삼호선_아이디 = 지하철_노선_응답에서_노선_아이디_추출(지하철_노선_생성_요청(노선_생성_요청_본문("삼호선", "orange", 교대역_아이디, 남부터미널역_아이디, 2L))); + 이호선_아이디 = 지하철_노선_응답에서_노선_아이디_추출(지하철_노선_생성_요청(노선_생성_요청_본문("2호선", "green", 교대역_아이디, 강남역_아이디, 10L, 10L))); + 신분당선_아이디 = 지하철_노선_응답에서_노선_아이디_추출(지하철_노선_생성_요청(노선_생성_요청_본문("신분당선", "red", 강남역_아이디, 양재역_아이디, 10L, 10L))); + 삼호선_아이디 = 지하철_노선_응답에서_노선_아이디_추출(지하철_노선_생성_요청(노선_생성_요청_본문("삼호선", "orange", 교대역_아이디, 남부터미널역_아이디, 2L, 2L))); - 지하철_구간_등록_요청(삼호선_아이디, 구간_등록_요청_본문(남부터미널역_아이디, 양재역_아이디, 3L)); + 지하철_구간_등록_요청(삼호선_아이디, 구간_등록_요청_본문(남부터미널역_아이디, 양재역_아이디, 3L, 3L)); } diff --git a/src/test/java/nextstep/subway/acceptance/LineAcceptanceTest.java b/src/test/java/nextstep/subway/acceptance/LineAcceptanceTest.java index 0a84f1566..a206dae00 100644 --- a/src/test/java/nextstep/subway/acceptance/LineAcceptanceTest.java +++ b/src/test/java/nextstep/subway/acceptance/LineAcceptanceTest.java @@ -60,7 +60,8 @@ void createLine() { COLOR_ONE, 강남역_아이디, 교대역_아이디, - DISTANCE + DISTANCE, + 10L )); // then diff --git a/src/test/java/nextstep/subway/acceptance/LineSectionAcceptanceTest.java b/src/test/java/nextstep/subway/acceptance/LineSectionAcceptanceTest.java index 3166645d3..4c31f8ade 100644 --- a/src/test/java/nextstep/subway/acceptance/LineSectionAcceptanceTest.java +++ b/src/test/java/nextstep/subway/acceptance/LineSectionAcceptanceTest.java @@ -54,6 +54,7 @@ void addSection() { ExtractableResponse 지하철_구간_등록_응답 = 지하철_구간_등록_요청(이호선_아이디, 구간_등록_요청_본문( 교대역_아이디, 봉천역_아이디, + 10L, 10L )); @@ -83,6 +84,7 @@ void addSectionWithInvalidUpStation() { ExtractableResponse 지하철_구간_등록_응답 = 지하철_구간_등록_요청(이호선_아이디, 구간_등록_요청_본문( 낙성대역_아이디, 봉천역_아이디, + 10L, 10L )); @@ -106,7 +108,7 @@ void addSectionWithAlreadyExistsStation() { Long 이호선_아이디 = 지하철_노선_응답에서_노선_아이디_추출(강남_교대_이호선_응답); Long 교대역_아이디 = 지하철_노선_응답에서_노선의_하행_종점역_아이디_추출(강남_교대_이호선_응답); Long 봉천역_아이디 = 지하철역_응답에서_역_아이디_추출(지하철_역_생성_요청(봉천역_생성_요청_본문())); - 지하철_구간_등록_요청(이호선_아이디, 구간_등록_요청_본문(교대역_아이디, 봉천역_아이디, 10L)); + 지하철_구간_등록_요청(이호선_아이디, 구간_등록_요청_본문(교대역_아이디, 봉천역_아이디, 10L, 10L)); Long 서울대입구역_아이디 = 지하철역_응답에서_역_아이디_추출(지하철_역_생성_요청(서울대입구역_생성_요청_본문())); @@ -114,6 +116,7 @@ void addSectionWithAlreadyExistsStation() { ExtractableResponse 지하철_구간_등록_응답 = 지하철_구간_등록_요청(이호선_아이디, 구간_등록_요청_본문( 서울대입구역_아이디, 봉천역_아이디, + 10L, 10L )); @@ -133,7 +136,7 @@ void addSectionWithAlreadyExistsStation() { @Test void addSectionWithIndex() { // given - ExtractableResponse 강남_봉천_이호선_응답 = 지하철_노선_생성_요청(강남역_봉천역_구간_이호선_생성_요청(10L)); + ExtractableResponse 강남_봉천_이호선_응답 = 지하철_노선_생성_요청(강남역_봉천역_구간_이호선_생성_요청()); Long 이호선_아이디 = 지하철_노선_응답에서_노선_아이디_추출(강남_봉천_이호선_응답); Long 강남역_아이디 = 지하철_노선_응답에서_노선의_상행_종점역_아이디_추출(강남_봉천_이호선_응답); Long 봉천역_아이디 = 지하철_노선_응답에서_노선의_하행_종점역_아이디_추출(강남_봉천_이호선_응답); @@ -143,6 +146,7 @@ void addSectionWithIndex() { ExtractableResponse 지하철_구간_등록_응답 = 지하철_구간_등록_요청(이호선_아이디, 구간_등록_요청_본문( 강남역_아이디, 서울대입구역_아이디, + 5L, 5L )); @@ -169,7 +173,7 @@ void removeSection() { Long 이호선_아이디 = 지하철_노선_응답에서_노선_아이디_추출(강남_교대_이호선_응답); Long 교대역_아이디 = 지하철_노선_응답에서_노선의_하행_종점역_아이디_추출(강남_교대_이호선_응답); Long 봉천역_아이디 = 지하철역_응답에서_역_아이디_추출(지하철_역_생성_요청(봉천역_생성_요청_본문())); - 지하철_구간_등록_요청(이호선_아이디, 구간_등록_요청_본문(교대역_아이디, 봉천역_아이디, 10L)); + 지하철_구간_등록_요청(이호선_아이디, 구간_등록_요청_본문(교대역_아이디, 봉천역_아이디, 10L, 10L)); // when ExtractableResponse 지하철_구간_삭제_응답 = 지하철_구간_삭제_요청(이호선_아이디, 봉천역_아이디); @@ -217,8 +221,8 @@ void removeSectionWithIndex() { Long 강남역_아이디 = 지하철역_응답에서_역_아이디_추출(지하철_역_생성_요청(강남역_생성_요청_본문())); Long 교대역_아이디 = 지하철역_응답에서_역_아이디_추출(지하철_역_생성_요청(교대역_생성_요청_본문())); Long 낙성대역_아이디 = 지하철역_응답에서_역_아이디_추출(지하철_역_생성_요청(낙성대역_생성_요청_본문())); - Long 이호선_아이디 = 지하철_노선_응답에서_노선_아이디_추출(지하철_노선_생성_요청(노선_생성_요청_본문(이호선_이름, 이호선_색, 강남역_아이디, 교대역_아이디, 10L))); - 지하철_구간_등록_요청(이호선_아이디, 구간_등록_요청_본문(교대역_아이디, 낙성대역_아이디, 10L)); + Long 이호선_아이디 = 지하철_노선_응답에서_노선_아이디_추출(지하철_노선_생성_요청(노선_생성_요청_본문(이호선_이름, 이호선_색, 강남역_아이디, 교대역_아이디, 10L, 10L))); + 지하철_구간_등록_요청(이호선_아이디, 구간_등록_요청_본문(교대역_아이디, 낙성대역_아이디, 10L, 10L)); // when ExtractableResponse 지하철_구간_삭제_응답 = 지하철_구간_삭제_요청(이호선_아이디, 교대역_아이디); diff --git a/src/test/java/nextstep/subway/acceptance/PathAcceptanceTest.java b/src/test/java/nextstep/subway/acceptance/PathAcceptanceTest.java index 05057f7a7..bf97ac7c1 100644 --- a/src/test/java/nextstep/subway/acceptance/PathAcceptanceTest.java +++ b/src/test/java/nextstep/subway/acceptance/PathAcceptanceTest.java @@ -14,6 +14,7 @@ import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; import nextstep.subway.acceptance.step.PathSteps; +import nextstep.subway.domain.enums.PathSearchType; import nextstep.utils.context.AcceptanceTest; import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.DisplayName; @@ -38,14 +39,14 @@ class PathAcceptanceTest { * When 출발역과 도착역을 기준으로 경로를 조회하면 * Then 최단거리를 기준으로 경로를 조회한다. */ - @DisplayName("지하철역 경로를 조회한다.") + @DisplayName("지하철역 최단 거리 기준 경로를 조회한다.") @Test void getPaths() { // given 이호선_삼호선_신분당선_노선의_구간_존재(); // when - ExtractableResponse 지하철_경로_조회_응답 = 지하철_경로_조회_요청(교대역_아이디, 양재역_아이디); + ExtractableResponse 지하철_경로_조회_응답 = 지하철_경로_조회_요청(교대역_아이디, 양재역_아이디, PathSearchType.DISTANCE); // then SoftAssertions.assertSoftly(softAssertions -> { softAssertions.assertThat(지하철_경로_조회_응답.statusCode()).isEqualTo(HttpStatus.OK.value()); @@ -70,11 +71,11 @@ void getPaths() { 양재역_아이디 = 지하철역_응답에서_역_아이디_추출(지하철_역_생성_요청(지하철역_생성_요청_본문("양재역"))); 남부터미널역_아이디 = 지하철역_응답에서_역_아이디_추출(지하철_역_생성_요청(지하철역_생성_요청_본문("남부터미널역"))); - 이호선_아이디 = 지하철_노선_응답에서_노선_아이디_추출(지하철_노선_생성_요청(노선_생성_요청_본문("2호선", "green", 교대역_아이디, 강남역_아이디, 10L))); - 신분당선_아이디 = 지하철_노선_응답에서_노선_아이디_추출(지하철_노선_생성_요청(노선_생성_요청_본문("신분당선", "red", 강남역_아이디, 양재역_아이디, 10L))); - 삼호선_아이디 = 지하철_노선_응답에서_노선_아이디_추출(지하철_노선_생성_요청(노선_생성_요청_본문("삼호선", "orange", 교대역_아이디, 남부터미널역_아이디, 2L))); + 이호선_아이디 = 지하철_노선_응답에서_노선_아이디_추출(지하철_노선_생성_요청(노선_생성_요청_본문("2호선", "green", 교대역_아이디, 강남역_아이디, 10L, 10L))); + 신분당선_아이디 = 지하철_노선_응답에서_노선_아이디_추출(지하철_노선_생성_요청(노선_생성_요청_본문("신분당선", "red", 강남역_아이디, 양재역_아이디, 10L, 10L))); + 삼호선_아이디 = 지하철_노선_응답에서_노선_아이디_추출(지하철_노선_생성_요청(노선_생성_요청_본문("삼호선", "orange", 교대역_아이디, 남부터미널역_아이디, 2L, 2L))); - 지하철_구간_등록_요청(삼호선_아이디, 구간_등록_요청_본문(남부터미널역_아이디, 양재역_아이디, 3L)); + 지하철_구간_등록_요청(삼호선_아이디, 구간_등록_요청_본문(남부터미널역_아이디, 양재역_아이디, 3L, 3L)); } } diff --git a/src/test/java/nextstep/subway/acceptance/step/PathSteps.java b/src/test/java/nextstep/subway/acceptance/step/PathSteps.java index 8981f59d6..dd0a14b2d 100644 --- a/src/test/java/nextstep/subway/acceptance/step/PathSteps.java +++ b/src/test/java/nextstep/subway/acceptance/step/PathSteps.java @@ -4,16 +4,19 @@ import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; import java.util.List; +import nextstep.subway.domain.enums.PathSearchType; import org.springframework.http.MediaType; public class PathSteps { public static final String SUBWAY_PATH_BASE_PATH = "/paths"; - public static ExtractableResponse 지하철_경로_조회_요청(Long sourceStationId, Long targetStationId) { + public static ExtractableResponse 지하철_경로_조회_요청(Long sourceStationId, Long targetStationId, + PathSearchType searchType) { return RestAssured.given().log().all() .queryParam("source", sourceStationId) .queryParam("target", targetStationId) + .queryParam("type", searchType.name()) .accept(MediaType.APPLICATION_JSON_VALUE) .when().get(SUBWAY_PATH_BASE_PATH) .then().log().all() @@ -33,4 +36,12 @@ public class PathSteps { public static long 지하철역_경로_조회_응답에서_경로_거리_추출(ExtractableResponse 지하철_경로_조회_응답) { return 지하철_경로_조회_응답.jsonPath().getLong("distance"); } + + public static long 지하철역_경로_조회_응답에서_경로_시간_추출(ExtractableResponse 지하철_경로_조회_응답) { + return 지하철_경로_조회_응답.jsonPath().getLong("duration"); + } + + public static int 지하철역_경로_조회_응답에서_지하철_요금_추출(ExtractableResponse 지하철_경로_조회_응답) { + return 지하철_경로_조회_응답.jsonPath().getInt("fare"); + } } diff --git a/src/test/java/nextstep/subway/fixture/LineFixture.java b/src/test/java/nextstep/subway/fixture/LineFixture.java index bcbe1e839..c42cd9742 100644 --- a/src/test/java/nextstep/subway/fixture/LineFixture.java +++ b/src/test/java/nextstep/subway/fixture/LineFixture.java @@ -1,9 +1,9 @@ package nextstep.subway.fixture; import java.util.Map; +import nextstep.subway.acceptance.step.StationSteps; import nextstep.subway.application.dto.response.StationResponse; import nextstep.subway.domain.entity.Line; -import nextstep.subway.acceptance.step.StationSteps; import org.springframework.test.util.ReflectionTestUtils; public class LineFixture { @@ -18,19 +18,19 @@ public class LineFixture { StationResponse 서울역 = StationSteps.지하철_역_생성_요청(StationFixture.서울역_생성_요청_본문()).as(StationResponse.class); StationResponse 청량리역 = StationSteps.지하철_역_생성_요청(StationFixture.청량리역_생성_요청_본문()).as(StationResponse.class); return 노선_생성_요청_본문(일호선_이름, 일호선_색, 서울역.getId(), 청량리역.getId(), - 10L); + 10L, 10L); } public static Map 강남역_교대역_구간_이호선_생성_요청() { StationResponse 강남역 = StationSteps.지하철_역_생성_요청(StationFixture.강남역_생성_요청_본문()).as(StationResponse.class); StationResponse 교대역 = StationSteps.지하철_역_생성_요청(StationFixture.교대역_생성_요청_본문()).as(StationResponse.class); - return 노선_생성_요청_본문(이호선_이름, 이호선_색, 강남역.getId(), 교대역.getId(), 10L); + return 노선_생성_요청_본문(이호선_이름, 이호선_색, 강남역.getId(), 교대역.getId(), 10L, 10L); } - public static Map 강남역_봉천역_구간_이호선_생성_요청(long distance) { + public static Map 강남역_봉천역_구간_이호선_생성_요청() { StationResponse 강남역 = StationSteps.지하철_역_생성_요청(StationFixture.강남역_생성_요청_본문()).as(StationResponse.class); StationResponse 봉천역 = StationSteps.지하철_역_생성_요청(StationFixture.봉천역_생성_요청_본문()).as(StationResponse.class); - return 노선_생성_요청_본문(이호선_이름, 이호선_색, 강남역.getId(), 봉천역.getId(), distance); + return 노선_생성_요청_본문(이호선_이름, 이호선_색, 강남역.getId(), 봉천역.getId(), 10L, 10L); } public static Map 노선_생성_요청_본문( @@ -38,10 +38,11 @@ public class LineFixture { String color, Long upStationId, Long downStationId, - Long distance + Long distance, + Long duration ) { return Map.of("name", name, "color", color, "upStationId", upStationId, "downStationId", downStationId, - "distance", distance); + "distance", distance, "duration", duration); } public static Map 노선_수정_요청( diff --git a/src/test/java/nextstep/subway/fixture/SectionFixture.java b/src/test/java/nextstep/subway/fixture/SectionFixture.java index 67de189e4..3bebc3254 100644 --- a/src/test/java/nextstep/subway/fixture/SectionFixture.java +++ b/src/test/java/nextstep/subway/fixture/SectionFixture.java @@ -8,20 +8,21 @@ public class SectionFixture { - public static Map 구간_등록_요청_본문(Long upStationId, Long downStationId, Long distance) { + public static Map 구간_등록_요청_본문(Long upStationId, Long downStationId, Long distance, long duration) { return Map.of( "upStationId", upStationId, "downStationId", downStationId, - "distance", distance + "distance", distance, + "duration", duration ); } - public static Section giveOne(Line line, Station upStation, Station downStation, long distance) { - return new Section(line, upStation, downStation, distance); + public static Section giveOne(Line line, Station upStation, Station downStation, long distance, long duration) { + return new Section(line, upStation, downStation, distance, duration); } - public static Section giveOne(long id, Line line, Station upStation, Station downStation, long distance) { - Section section = new Section(line, upStation, downStation, distance); + public static Section giveOne(long id, Line line, Station upStation, Station downStation, long distance, long duration) { + Section section = new Section(line, upStation, downStation, distance, duration); ReflectionTestUtils.setField(section, "id", id); return section; } diff --git a/src/test/java/nextstep/subway/unit/LineServiceMockTest.java b/src/test/java/nextstep/subway/unit/LineServiceMockTest.java index 8a738f107..e485d624f 100644 --- a/src/test/java/nextstep/subway/unit/LineServiceMockTest.java +++ b/src/test/java/nextstep/subway/unit/LineServiceMockTest.java @@ -71,9 +71,9 @@ void setUp() { 이호선 = LineFixture.giveOne(1L, 이호선_이름, 이호선_색); 일호선 = LineFixture.giveOne(2L, 일호선_이름, 일호선_색); - 강남역_교대역_구간 = SectionFixture.giveOne(1L, 이호선, 강남역, 교대역, 10L); - 교대역_낙성대역_구간 = SectionFixture.giveOne(2L, 이호선, 교대역, 낙성대역, 10L); - 서울역_청량리역_구간 = SectionFixture.giveOne(3L, 일호선, 서울역, 청량리역, 10L); + 강남역_교대역_구간 = SectionFixture.giveOne(1L, 이호선, 강남역, 교대역, 10L, 10L); + 교대역_낙성대역_구간 = SectionFixture.giveOne(2L, 이호선, 교대역, 낙성대역, 10L, 10L); + 서울역_청량리역_구간 = SectionFixture.giveOne(3L, 일호선, 서울역, 청량리역, 10L, 10L); } @DisplayName("노선의 구간 추가 서비스 단위 테스트") @@ -86,7 +86,7 @@ void addSection() { given(stationService.getStationById(낙성대역.getId())).willReturn(낙성대역); // when - AddSectionRequest 교대역_낙성대역_구간_추가_요청 = new AddSectionRequest(교대역.getId(), 낙성대역.getId(), 10L); + AddSectionRequest 교대역_낙성대역_구간_추가_요청 = new AddSectionRequest(교대역.getId(), 낙성대역.getId(), 10L, 10L); LineResponse result = lineService.addSection(이호선.getId(), 교대역_낙성대역_구간_추가_요청); // then diff --git a/src/test/java/nextstep/subway/unit/LineServiceTest.java b/src/test/java/nextstep/subway/unit/LineServiceTest.java index 85c06b3c1..6cd3e1444 100644 --- a/src/test/java/nextstep/subway/unit/LineServiceTest.java +++ b/src/test/java/nextstep/subway/unit/LineServiceTest.java @@ -69,9 +69,9 @@ void setUp() { 이호선 = lineRepository.save(LineFixture.giveOne(1L, 이호선_이름, 이호선_색)); 일호선 = lineRepository.save(LineFixture.giveOne(2L, 일호선_이름, 일호선_색)); - 강남역_교대역_구간 = SectionFixture.giveOne(1L, 이호선, 강남역, 교대역, 10L); - 교대역_낙성대역_구간 = SectionFixture.giveOne(2L, 이호선, 교대역, 낙성대역, 10L); - 서울역_청량리역_구간 = SectionFixture.giveOne(3L, 일호선, 서울역, 청량리역, 10L); + 강남역_교대역_구간 = SectionFixture.giveOne(1L, 이호선, 강남역, 교대역, 10L, 10L); + 교대역_낙성대역_구간 = SectionFixture.giveOne(2L, 이호선, 교대역, 낙성대역, 10L, 10L); + 서울역_청량리역_구간 = SectionFixture.giveOne(3L, 일호선, 서울역, 청량리역, 10L, 10L); } @DisplayName("노선의 구간 추가 서비스 단위 테스트") @@ -81,7 +81,7 @@ void addSection() { Line 이호선 = 강남역_교대역_구간_이호선(); // when - AddSectionRequest 교대역_낙성대역_구간_추가_요청 = new AddSectionRequest(교대역.getId(), 낙성대역.getId(), 10L); + AddSectionRequest 교대역_낙성대역_구간_추가_요청 = new AddSectionRequest(교대역.getId(), 낙성대역.getId(), 10L, 10L); LineResponse result = lineService.addSection(이호선.getId(), 교대역_낙성대역_구간_추가_요청); // then diff --git a/src/test/java/nextstep/subway/unit/LineTest.java b/src/test/java/nextstep/subway/unit/LineTest.java index 28f82af3d..41a4e8566 100644 --- a/src/test/java/nextstep/subway/unit/LineTest.java +++ b/src/test/java/nextstep/subway/unit/LineTest.java @@ -39,8 +39,8 @@ void setUp() { 이호선 = LineFixture.giveOne(1L, 이호선_이름, 이호선_색); - 강남역_교대역_구간 = SectionFixture.giveOne(1L, 이호선, 강남역, 교대역, 10L); - 교대역_낙성대역_구간 = SectionFixture.giveOne(2L, 이호선, 교대역, 낙성대역, 10L); + 강남역_교대역_구간 = SectionFixture.giveOne(1L, 이호선, 강남역, 교대역, 10L, 10L); + 교대역_낙성대역_구간 = SectionFixture.giveOne(2L, 이호선, 교대역, 낙성대역, 10L, 10L); } diff --git a/src/test/java/nextstep/subway/unit/SectionsTest.java b/src/test/java/nextstep/subway/unit/SectionsTest.java index 5008bf94e..d1eaa218e 100644 --- a/src/test/java/nextstep/subway/unit/SectionsTest.java +++ b/src/test/java/nextstep/subway/unit/SectionsTest.java @@ -48,10 +48,11 @@ void setUp() { 이호선 = LineFixture.giveOne(1L, 이호선_이름, 이호선_색); - 강남역_낙성대역_구간 = SectionFixture.giveOne(1L, 이호선, 강남역, 낙성대역, 10L); - 낙성대역_봉천역_구간 = SectionFixture.giveOne(2L, 이호선, 낙성대역, 봉천역, 10L); - 강남역_교대역_구간 = SectionFixture.giveOne(3L, 이호선, 강남역, 교대역, 5L); - 강남역_낙성대역_구간의_길이와_같은_강남역_교대역_구간 = SectionFixture.giveOne(4L, 이호선, 강남역, 교대역, 강남역_낙성대역_구간.getDistance()); + 강남역_낙성대역_구간 = SectionFixture.giveOne(1L, 이호선, 강남역, 낙성대역, 10L, 10L); + 낙성대역_봉천역_구간 = SectionFixture.giveOne(2L, 이호선, 낙성대역, 봉천역, 10L, 10L); + 강남역_교대역_구간 = SectionFixture.giveOne(3L, 이호선, 강남역, 교대역, 5L, 5L); + 강남역_낙성대역_구간의_길이와_같은_강남역_교대역_구간 = SectionFixture + .giveOne(4L, 이호선, 강남역, 교대역, 강남역_낙성대역_구간.getDistance(), 강남역_낙성대역_구간.getDuration()); } diff --git a/src/test/java/nextstep/subway/unit/SubwayFareTest.java b/src/test/java/nextstep/subway/unit/SubwayFareTest.java new file mode 100644 index 000000000..8dabf7dd1 --- /dev/null +++ b/src/test/java/nextstep/subway/unit/SubwayFareTest.java @@ -0,0 +1,118 @@ +package nextstep.subway.unit; + +import static nextstep.subway.fixture.LineFixture.이호선_색; +import static nextstep.subway.fixture.LineFixture.이호선_이름; +import static nextstep.subway.fixture.StationFixture.강남역_이름; +import static nextstep.subway.fixture.StationFixture.교대역_이름; +import java.util.List; +import java.util.stream.Stream; +import nextstep.subway.domain.entity.Line; +import nextstep.subway.domain.entity.Path; +import nextstep.subway.domain.entity.Station; +import nextstep.subway.domain.entity.SubWayFare; +import nextstep.subway.domain.entity.SubwayMap; +import nextstep.subway.domain.enums.PathSearchType; +import nextstep.subway.fixture.LineFixture; +import nextstep.subway.fixture.SectionFixture; +import nextstep.subway.fixture.StationFixture; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class SubwayFareTest { + + + private final Station 교대역 = StationFixture.giveOne(1L, 교대역_이름); + private final Station 강남역 = StationFixture.giveOne(2L, 강남역_이름); + private final Line 이호선 = LineFixture.giveOne(1L, 이호선_이름, 이호선_색); + private final Line 신분당선 = LineFixture.giveOne(2L, "신분당선", "red"); + private SubWayFare subWayFare; + + + @ParameterizedTest + @MethodSource("defaultFareArguments") + void 지하철_요금_계산_기본_운임비(long distance, int expectedFare) { + // given + 이호선.addSection(SectionFixture.giveOne(1L, 이호선, 교대역, 강남역, distance, 1)); + var subwayMap = new SubwayMap(List.of(이호선, 신분당선)); + Path path = subwayMap.findShortestPath(교대역, 강남역, PathSearchType.DISTANCE).get(); + subWayFare = new SubWayFare(path); + + // when + int fare = subWayFare.calculateFare(); + + // then + SoftAssertions.assertSoftly(softAssertions -> { + softAssertions.assertThat(fare).isEqualTo(expectedFare); + }); + } + + @ParameterizedTest + @MethodSource("over10kmFareArguments") + void 지하철_요금_계산_10km_초과(long distance, int expectedFare) { + // given + 이호선.addSection(SectionFixture.giveOne(1L, 이호선, 교대역, 강남역, distance, 1)); + var subwayMap = new SubwayMap(List.of(이호선, 신분당선)); + Path path = subwayMap.findShortestPath(교대역, 강남역, PathSearchType.DISTANCE).get(); + subWayFare = new SubWayFare(path); + + // when + int fare = subWayFare.calculateFare(); + + // then + SoftAssertions.assertSoftly(softAssertions -> { + softAssertions.assertThat(fare).isEqualTo(expectedFare); + }); + } + + @ParameterizedTest + @MethodSource("over50kmFareArguments") + void 지하철_요금_계산_50km_초과(long distance, int expectedFare) { + // given + 이호선.addSection(SectionFixture.giveOne(1L, 이호선, 교대역, 강남역, distance, 1)); + var subwayMap = new SubwayMap(List.of(이호선, 신분당선)); + Path path = subwayMap.findShortestPath(교대역, 강남역, PathSearchType.DISTANCE).get(); + subWayFare = new SubWayFare(path); + + // when + int fare = subWayFare.calculateFare(); + + // then + SoftAssertions.assertSoftly(softAssertions -> { + softAssertions.assertThat(fare).isEqualTo(expectedFare); + }); + } + + + private static Stream defaultFareArguments() { + return Stream.of( + Arguments.of(0L, 1250), + Arguments.of(5L, 1250), + Arguments.of(9L, 1250), + Arguments.of(10L, 1250) + ); + } + + private static Stream over10kmFareArguments() { + return Stream.of( + Arguments.of(11L, 1350), + Arguments.of(14L, 1350), + Arguments.of(16L, 1450), + Arguments.of(21L, 1550), + Arguments.of(50L, 2050) + ); + } + + private static Stream over50kmFareArguments() { + return Stream.of( + Arguments.of(51L, 1850), + Arguments.of(58L, 1850), + Arguments.of(59L, 1950), + Arguments.of(65L, 1950), + Arguments.of(66L, 1950), + Arguments.of(67L, 2050) + ); + } + +} diff --git a/src/test/java/nextstep/subway/unit/PathFinderTest.java b/src/test/java/nextstep/subway/unit/SubwayMapTest.java similarity index 77% rename from src/test/java/nextstep/subway/unit/PathFinderTest.java rename to src/test/java/nextstep/subway/unit/SubwayMapTest.java index 879b81175..29f4302ef 100644 --- a/src/test/java/nextstep/subway/unit/PathFinderTest.java +++ b/src/test/java/nextstep/subway/unit/SubwayMapTest.java @@ -9,9 +9,10 @@ import java.util.Optional; import nextstep.subway.domain.entity.Line; import nextstep.subway.domain.entity.Path; -import nextstep.subway.domain.entity.PathFinder; import nextstep.subway.domain.entity.Section; import nextstep.subway.domain.entity.Station; +import nextstep.subway.domain.entity.SubwayMap; +import nextstep.subway.domain.enums.PathSearchType; import nextstep.subway.fixture.LineFixture; import nextstep.subway.fixture.SectionFixture; import nextstep.subway.fixture.StationFixture; @@ -19,7 +20,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -class PathFinderTest { +class SubwayMapTest { private Station 교대역; @@ -53,10 +54,10 @@ class PathFinderTest { 삼호선 = LineFixture.giveOne(2L, "3호선", "orange"); 신분당선 = LineFixture.giveOne(3L, "신분당선", "red"); - 교대역_강남역_구간 = SectionFixture.giveOne(1L, 이호선, 교대역, 강남역, 10L); - 교대역_남부터미널역_구간 = SectionFixture.giveOne(2L, 삼호선, 교대역, 남부터미널역, 2L); - 남부터미널역_양재역_구간 = SectionFixture.giveOne(3L, 삼호선, 남부터미널역, 양재역, 3L); - 강남역_양재역_구간 = SectionFixture.giveOne(4L, 신분당선, 강남역, 양재역, 10L); + 교대역_강남역_구간 = SectionFixture.giveOne(1L, 이호선, 교대역, 강남역, 10L, 10L); + 교대역_남부터미널역_구간 = SectionFixture.giveOne(2L, 삼호선, 교대역, 남부터미널역, 2L, 2L); + 남부터미널역_양재역_구간 = SectionFixture.giveOne(3L, 삼호선, 남부터미널역, 양재역, 3L, 3L); + 강남역_양재역_구간 = SectionFixture.giveOne(4L, 신분당선, 강남역, 양재역, 10L, 10L); 이호선.addSection(교대역_강남역_구간); 삼호선.addSection(교대역_남부터미널역_구간); @@ -69,15 +70,15 @@ class PathFinderTest { void findShorPath() { // given 이호선_삼호선_신분당선_노선의_구간_존재(); - PathFinder pathFinder = new PathFinder(List.of(이호선, 삼호선, 신분당선)); + SubwayMap subwayMap = new SubwayMap(List.of(이호선, 삼호선, 신분당선)); // when - Optional 교대역_양재역_최단경로 = pathFinder.findShortestPath(교대역, 양재역); + Optional 교대역_양재역_최단경로 = subwayMap.findShortestPath(교대역, 양재역, PathSearchType.DISTANCE); // then SoftAssertions.assertSoftly(softAssertions -> { Path shortestPath = 교대역_양재역_최단경로.orElseThrow(); - softAssertions.assertThat(shortestPath.getStations()).containsExactly(교대역, 남부터미널역, 양재역); + softAssertions.assertThat(shortestPath.getStationsOfPath()).containsExactly(교대역, 남부터미널역, 양재역); softAssertions.assertThat(shortestPath.getDistance()).isEqualTo(5L); }); @@ -88,11 +89,11 @@ void findShorPath() { void findShorPathWithError() { // given 이호선_삼호선_신분당선_노선의_구간_존재(); - PathFinder pathFinder = new PathFinder(List.of(이호선, 삼호선, 신분당선)); + SubwayMap subwayMap = new SubwayMap(List.of(이호선, 삼호선, 신분당선)); // when Throwable catchThrowable = catchThrowable(() -> { - pathFinder.findShortestPath(교대역, 교대역); + subwayMap.findShortestPath(교대역, 교대역, PathSearchType.DISTANCE); }); // then @@ -108,10 +109,10 @@ void findShorPathWithError() { void findShorPathWithError2() { // given 이호선_삼호선_신분당선_노선의_구간_존재(); - PathFinder pathFinder = new PathFinder(List.of(이호선, 신분당선)); + SubwayMap subwayMap = new SubwayMap(List.of(이호선, 신분당선)); // when - Optional 교대역_남부터미널역_경로 = pathFinder.findShortestPath(교대역, 남부터미널역); + Optional 교대역_남부터미널역_경로 = subwayMap.findShortestPath(교대역, 남부터미널역, PathSearchType.DISTANCE); // then SoftAssertions.assertSoftly(softAssertions -> { @@ -126,11 +127,11 @@ void findShorPathWithError2() { void findShorPathWithError3() { // given 이호선_삼호선_신분당선_노선의_구간_존재(); - PathFinder pathFinder = new PathFinder(List.of(이호선, 삼호선, 신분당선)); + SubwayMap subwayMap = new SubwayMap(List.of(이호선, 삼호선, 신분당선)); Station 존재하지_않는_역 = StationFixture.giveOne(Long.MAX_VALUE, "폐쇄역"); // when - Optional 존재하지_않는_경로 = pathFinder.findShortestPath(존재하지_않는_역, 양재역); + Optional 존재하지_않는_경로 = subwayMap.findShortestPath(존재하지_않는_역, 양재역, PathSearchType.DISTANCE); // then SoftAssertions.assertSoftly(softAssertions -> { diff --git a/src/test/resources/features/path.feature b/src/test/resources/features/path.feature index e3835f3f1..c94ee690f 100644 --- a/src/test/resources/features/path.feature +++ b/src/test/resources/features/path.feature @@ -1,11 +1,11 @@ Feature: 지하철역 경로 관련 기능 # /** -# * 교대역 --- 2호선, 10 ---- 강남역 -# * | | -# * 3호선, 2 신분당선, 10 -# * | | -# * 남부터미널역 --- 3호선, 3 --- 양재 +# * 교대역 --- 2호선, 10, 1 ---- 강남역 +# * | | +# * 3호선, 2, 20 신분당선, 10, 1 +# * | | +# * 남부터미널역 -- 3호선, 3, 30 ---- 양재 # */ Background: 이호선, 삼호선, 신분당선 노선의 구간 Given 지하철역들을 생성 요청하고 @@ -15,14 +15,24 @@ Feature: 지하철역 경로 관련 기능 | 양재역 | | 남부터미널역 | And 지하철 노선들을 생성 요청하고 - | name | color | upStation | downStation | distance | - | 2호선 | green | 교대역 | 강남역 | 10 | - | 신분당선 | red | 강남역 | 양재역 | 10 | - | 3호선 | orange | 교대역 | 남부터미널역 | 2 | + | name | color | upStation | downStation | distance | duration | + | 2호선 | green | 교대역 | 강남역 | 10 | 1 | + | 신분당선 | red | 강남역 | 양재역 | 10 | 1 | + | 3호선 | orange | 교대역 | 남부터미널역 | 2 | 20 | And 지하철 구간을 등록 요청하고 - | lineName | upStation | downStation | distance | - | 3호선 | 남부터미널역 | 양재역 | 3 | + | lineName | upStation | downStation | distance | duration | + | 3호선 | 남부터미널역 | 양재역 | 3 | 30 | + + Scenario: 지하철역 최단 거리 경로를 조회한다. + When "교대역"에서 "양재역"까지의 "최소 거리" 기준으로 경로 조회를 요청하면 + Then "최소 거리" 기준 "교대역,남부터미널역,양재역" 경로를 응답 + And 총 거리 5와 소요 시간 50을 함께 응답함 + And 이용 요금 1250도 함께 응답함 + + Scenario: 지하철역 최단 시간 경로를 조회한다. + When "양재역"에서 "교대역"까지의 "최소 시간" 기준으로 경로 조회를 요청하면 + Then "최소 시간" 기준 "양재역,강남역,교대역" 경로를 응답 + And 총 거리 20와 소요 시간 2을 함께 응답함 + And 이용 요금 1450도 함께 응답함 + - Scenario: 지하철역 최단 경로를 조회한다. - When "교대역"과 "양재역"의 경로를 조회하면 - Then 거리가 5인 "교대역,남부터미널역,양재역"경로가 조회된다