Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3단계 - 요금 정책 추가 #218

Open
wants to merge 6 commits into
base: artium59
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions src/main/java/nextstep/DataLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,23 @@
import nextstep.member.domain.RoleType;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

@Component
public class DataLoader {
private MemberRepository memberRepository;
private static final String PASSWORD ="password";

private final MemberRepository memberRepository;

public DataLoader(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}

public void loadData() {
memberRepository.save(new Member("[email protected]", "password", 20, Arrays.asList(RoleType.ROLE_ADMIN.name())));
memberRepository.save(new Member("[email protected]", "password", 20, Arrays.asList(RoleType.ROLE_MEMBER.name())));
memberRepository.save(new Member("[email protected]", PASSWORD, 20, List.of(RoleType.ROLE_ADMIN.name())));

memberRepository.save(new Member("[email protected]", PASSWORD, 9, List.of(RoleType.ROLE_MEMBER.name())));
memberRepository.save(new Member("[email protected]", PASSWORD, 17, List.of(RoleType.ROLE_MEMBER.name())));
memberRepository.save(new Member("[email protected]", PASSWORD, 30, List.of(RoleType.ROLE_MEMBER.name())));
}
}
19 changes: 15 additions & 4 deletions src/main/java/nextstep/subway/applicaion/FareCalculator.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,25 @@ public class FareCalculator {
private static final IntUnaryOperator BETWEEN_10_AND_50 = d -> (int) Math.ceil(d / BETWEEN_10_AND_50_DENOMINATOR) * 100;
private static final IntUnaryOperator OVER_50 = d -> (int) Math.ceil(d / OVER_50_DENOMINATOR) * 100;

public int calculateOverFare(int distance) {
public int calculateOverFare(int distance, int extraFare, int age) {
if (distance == 0) {
return 0;
}

int over50 = Math.max(distance - FIFTY, 0);
int between10and50 = Math.max(distance - over50 - MINIMUM_DISTANCE, 0);
final int over50 = Math.max(distance - FIFTY, 0);
final int between10and50 = Math.max(distance - over50 - MINIMUM_DISTANCE, 0);

return MINIMUM_FARE + BETWEEN_10_AND_50.applyAsInt(between10and50) + OVER_50.applyAsInt(over50);
final int price = MINIMUM_FARE + BETWEEN_10_AND_50.applyAsInt(between10and50) + OVER_50.applyAsInt(over50);

return calculateByAge(age, price + extraFare);
}

private int calculateByAge(int age, int price) {
if (age < 13 && age >= 6) {
return (price - 350) / 10 * 5;
} else if (age < 19 && age >= 13) {
return (price - 350) / 10 * 8;
}
return price;
Comment on lines +34 to +40
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

주석 없이는 쉽게 이해하기 힘든 코드라고 생각드네요 😢
(객체지향 생활 체조 규칙 2: else 예약어를 쓰지 않는다.)

할인 정책이 추가되거나 변경되면 조건문이 무한히 늘어날 수 을 것 같아요
정책을 효과적으로 관리할 수 있는 방법은 어떤게 있을까요?

}
}
2 changes: 1 addition & 1 deletion src/main/java/nextstep/subway/applicaion/LineService.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public LineService(LineRepository lineRepository, StationService stationService)

@Transactional
public LineResponse saveLine(LineRequest request) {
Line line = lineRepository.save(new Line(request.getName(), request.getColor()));
Line line = lineRepository.save(new Line(request.getName(), request.getColor(), request.getExtraFare()));
if (request.getUpStationId() != null && request.getDownStationId() != null && request.getDistance() != 0) {
Station upStation = stationService.findById(request.getUpStationId());
Station downStation = stationService.findById(request.getDownStationId());
Expand Down
16 changes: 10 additions & 6 deletions src/main/java/nextstep/subway/applicaion/PathService.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package nextstep.subway.applicaion;

import nextstep.member.application.MemberService;
import nextstep.subway.builder.PathResponseBuilder;
import nextstep.subway.applicaion.dto.PathResponse;
import nextstep.subway.constant.SearchType;
Expand All @@ -13,23 +14,26 @@

@Service
public class PathService {
private LineService lineService;
private StationService stationService;
private PathResponseBuilder pathResponseBuilder;
private final MemberService memberService;
private final LineService lineService;
private final StationService stationService;
private final PathResponseBuilder pathResponseBuilder;

public PathService(LineService lineService, StationService stationService, PathResponseBuilder pathResponseBuilder) {
public PathService(MemberService memberService, LineService lineService, StationService stationService, PathResponseBuilder pathResponseBuilder) {
this.memberService = memberService;
this.lineService = lineService;
this.stationService = stationService;
this.pathResponseBuilder = pathResponseBuilder;
}

public PathResponse findPath(Long source, Long target, SearchType searchType) {
public PathResponse findPath(String email, Long source, Long target, SearchType searchType) {
int age = memberService.findMember(email).getAge();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

회원가입 되어 있는 회원이 로그인 상태가 아니라면 이 서비스는 이용을 못할 것 같네요
요구사항에 있듯이 로그인 사용자의 경우 연령별 요금 계산을 적용하고
비회원(로그인하지 않은 상태)에서는 연령별 할인 정책이 적용되지 않은 상태에서 경로를 조회할 수 있으면 좋을 것 같아요

Station upStation = stationService.findById(source);
Station downStation = stationService.findById(target);
List<Line> lines = lineService.findLines();
SubwayMap subwayMap = new SubwayMap(lines);
Path path = subwayMap.findPath(upStation, downStation, searchType);

return pathResponseBuilder.build(path);
return pathResponseBuilder.build(path, age);
}
}
5 changes: 5 additions & 0 deletions src/main/java/nextstep/subway/applicaion/dto/LineRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public class LineRequest {
private Long downStationId;
private int distance;
private int duration;
private int extraFare;

public String getName() {
return name;
Expand All @@ -31,4 +32,8 @@ public int getDistance() {
public int getDuration() {
return duration;
}

public int getExtraFare() {
return extraFare;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,19 @@
@Component
public class PathResponseBuilder {

private FareCalculator fareCalculator;
private final FareCalculator fareCalculator;

public PathResponseBuilder(FareCalculator fareCalculator) {
this.fareCalculator = fareCalculator;
}

public PathResponse build(Path path) {
public PathResponse build(Path path, int age) {
List<StationResponse> stations = path.getStations().stream()
.map(StationResponse::of)
.collect(Collectors.toList());
int distance = path.extractDistance();
int duration = path.extractDuration();
int fare = fareCalculator.calculateOverFare(distance);
int fare = fareCalculator.calculateOverFare(distance, path.getExtraFare(), age);

return new PathResponse(stations, distance, duration, fare);
}
Expand Down
8 changes: 7 additions & 1 deletion src/main/java/nextstep/subway/domain/Line.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,18 @@ public class Line {
private Long id;
private String name;
private String color;
private int extraFare;

@Embedded
private Sections sections = new Sections();

public Line() {
}

public Line(String name, String color) {
public Line(String name, String color, int extraFare) {
this.name = name;
this.color = color;
this.extraFare = extraFare;
}

public Long getId() {
Expand All @@ -34,6 +36,10 @@ public String getColor() {
return color;
}

public int getExtraFare() {
return extraFare;
}

public List<Section> getSections() {
return sections.getSections();
}
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/nextstep/subway/domain/Path.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ public int extractDuration() {
return sections.totalDuration();
}

public int getExtraFare() {
return sections.getExtraFare();
}

public List<Station> getStations() {
return sections.getStations();
}
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/nextstep/subway/domain/Section.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ public int getDuration() {
return duration;
}

public int getExtraFare() {
return line.getExtraFare();
}

public boolean isSameUpStation(Station station) {
return this.upStation == station;
}
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/nextstep/subway/domain/Sections.java
Original file line number Diff line number Diff line change
Expand Up @@ -167,4 +167,8 @@ public int totalDistance() {
public int totalDuration() {
return sections.stream().mapToInt(Section::getDuration).sum();
}

public int getExtraFare() {
return sections.stream().mapToInt(Section::getExtraFare).max().orElse(0);
}
}
7 changes: 5 additions & 2 deletions src/main/java/nextstep/subway/ui/PathController.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import support.auth.authorization.AuthenticationPrincipal;
import support.auth.userdetails.User;

@RestController
public class PathController {
Expand All @@ -17,7 +19,8 @@ public PathController(PathService pathService) {
}

@GetMapping("/paths")
public ResponseEntity<PathResponse> findPath(@RequestParam Long source, @RequestParam Long target, @RequestParam SearchType searchType) {
return ResponseEntity.ok(pathService.findPath(source, target, searchType));
public ResponseEntity<PathResponse> findPath(@AuthenticationPrincipal User user,
@RequestParam Long source, @RequestParam Long target, @RequestParam SearchType searchType) {
return ResponseEntity.ok(pathService.findPath(user.getUsername(), source, target, searchType));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class AcceptanceTest {
private static final String EMAIL = "[email protected]";
private static final String PASSWORD = "password";
public static final String PASSWORD = "password";

@LocalServerPort
int port;
Expand Down
58 changes: 37 additions & 21 deletions src/test/java/nextstep/subway/acceptance/PathAcceptanceTest.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
package nextstep.subway.acceptance;

import io.restassured.RestAssured;
import io.restassured.response.ExtractableResponse;
import io.restassured.response.Response;
import nextstep.subway.constant.SearchType;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;

import static nextstep.subway.acceptance.LineSteps.지하철_노선에_지하철_구간_생성_요청;
import static nextstep.subway.acceptance.MemberSteps.로그인_되어_있음;
import static nextstep.subway.acceptance.PathSteps.searchType에_따른_두_역의_최단_경로_조회를_요청;
import static nextstep.subway.acceptance.StationSteps.지하철역_생성_요청;
import static org.assertj.core.api.Assertions.assertThat;

Expand Down Expand Up @@ -42,48 +45,60 @@ public void setUp() {
양재역 = 지하철역_생성_요청(관리자, "양재역").jsonPath().getLong("id");
남부터미널역 = 지하철역_생성_요청(관리자, "남부터미널역").jsonPath().getLong("id");

이호선 = 지하철_노선_생성_요청("2호선", "green", 교대역, 강남역, 10, 4);
신분당선 = 지하철_노선_생성_요청("신분당선", "red", 강남역, 양재역, 10, 4);
삼호선 = 지하철_노선_생성_요청("3호선", "orange", 교대역, 남부터미널역, 2, 10);
이호선 = 지하철_노선_생성_요청("2호선", "green", 교대역, 강남역, 10, 4, 200);
신분당선 = 지하철_노선_생성_요청("신분당선", "red", 강남역, 양재역, 10, 4, 1000);
삼호선 = 지하철_노선_생성_요청("3호선", "orange", 교대역, 남부터미널역, 2, 10, 300);

지하철_노선에_지하철_구간_생성_요청(관리자, 삼호선, createSectionCreateParams(남부터미널역, 양재역, 3, 1));
}

@DisplayName("두 역의 최단 거리 경로를 조회한다.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

테스트 내용이 변경됨에 따라 DisplayName도 변경하면 어떨까요?
경로와 시간, 요금까지 모두 조회할 수 있다는 표현이 포함되면 좋을 것 같아요

@Test
void findPathByDistance() {
@ParameterizedTest(name = "{index}: {2}")
@MethodSource("유저와_최단_거리_경로_요금")
void findPathByDistance(String email, int price, String message) {
// when
ExtractableResponse<Response> response = searchType에_따른_두_역의_최단_경로_조회를_요청(교대역, 양재역, SearchType.DISTANCE);
String accessToken = 로그인_되어_있음(email, PASSWORD);
ExtractableResponse<Response> response = searchType에_따른_두_역의_최단_경로_조회를_요청(accessToken, 교대역, 양재역, SearchType.DISTANCE);

// then
assertThat(response.jsonPath().getList("stations.id", Long.class)).containsExactly(교대역, 남부터미널역, 양재역);
assertThat(response.jsonPath().getInt("distance")).isEqualTo(5);
assertThat(response.jsonPath().getInt("duration")).isEqualTo(11);
assertThat(response.jsonPath().getInt("fare")).isEqualTo(1250);
assertThat(response.jsonPath().getInt("fare")).isEqualTo(price);
}

@DisplayName("두 역의 최단 시간 경로를 조회한다.")
@Test
void findPathByDuration() {
@ParameterizedTest(name = "{index}: {2}")
@MethodSource("유저와_최단_시간_경로_요금")
void findPathByDuration(String email, int price, String message) {
// when
ExtractableResponse<Response> response = searchType에_따른_두_역의_최단_경로_조회를_요청(교대역, 양재역, SearchType.DURATION);
String accessToken = 로그인_되어_있음(email, PASSWORD);
ExtractableResponse<Response> response = searchType에_따른_두_역의_최단_경로_조회를_요청(accessToken, 교대역, 양재역, SearchType.DURATION);

// then
assertThat(response.jsonPath().getList("stations.id", Long.class)).containsExactly(교대역, 강남역, 양재역);
assertThat(response.jsonPath().getInt("distance")).isEqualTo(20);
assertThat(response.jsonPath().getInt("duration")).isEqualTo(8);
assertThat(response.jsonPath().getInt("fare")).isEqualTo(1450);
assertThat(response.jsonPath().getInt("fare")).isEqualTo(price);
}

private ExtractableResponse<Response> searchType에_따른_두_역의_최단_경로_조회를_요청(Long source, Long target, SearchType searchType) {
return RestAssured
.given().log().all()
.accept(MediaType.APPLICATION_JSON_VALUE)
.when().get("/paths?source={sourceId}&target={targetId}&searchType={searchType}", source, target, searchType.name())
.then().log().all().extract();
private static Stream<Arguments> 유저와_최단_거리_경로_요금() {
return Stream.of(
Arguments.of("[email protected]", 600, "어린이 사용자는 350원을 공제한 금액의 50%를 할인받는다."),
Arguments.of("[email protected]", 960, "청소년 사용자는 350원을 공제한 금액의 20%를 할인받는다."),
Arguments.of("[email protected]", 1550, "성인 사용자는 할인 안받는다.")
);
}

private Long 지하철_노선_생성_요청(String name, String color, Long upStation, Long downStation, int distance, int duration) {
private static Stream<Arguments> 유저와_최단_시간_경로_요금() {
return Stream.of(
Arguments.of("[email protected]", 1050, "어린이 사용자는 350원을 공제한 금액의 50%를 할인받는다."),
Arguments.of("[email protected]", 1680, "청소년 사용자는 350원을 공제한 금액의 20%를 할인받는다."),
Arguments.of("[email protected]", 2450, "성인 사용자는 할인 안받는다.")
);
}

private Long 지하철_노선_생성_요청(String name, String color, Long upStation, Long downStation, int distance, int duration, int extraFare) {
Map<String, String> lineCreateParams;
lineCreateParams = new HashMap<>();
lineCreateParams.put("name", name);
Expand All @@ -92,6 +107,7 @@ void findPathByDuration() {
lineCreateParams.put("downStationId", downStation + "");
lineCreateParams.put("distance", distance + "");
lineCreateParams.put("duration", duration + "");
lineCreateParams.put("extraFare", extraFare + "");

return LineSteps.지하철_노선_생성_요청(관리자, lineCreateParams).jsonPath().getLong("id");
}
Expand Down
16 changes: 16 additions & 0 deletions src/test/java/nextstep/subway/acceptance/PathSteps.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package nextstep.subway.acceptance;

import io.restassured.response.ExtractableResponse;
import io.restassured.response.Response;
import nextstep.subway.constant.SearchType;
import org.springframework.http.MediaType;

public class PathSteps extends AcceptanceTestSteps {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PathSteps를 분리해 주셨네요 👍


public static ExtractableResponse<Response> searchType에_따른_두_역의_최단_경로_조회를_요청(String accessToken, Long source, Long target, SearchType searchType) {
return given(accessToken)
.accept(MediaType.APPLICATION_JSON_VALUE)
.when().get("/paths?source={sourceId}&target={targetId}&searchType={searchType}", source, target, searchType.name())
.then().log().all().extract();
}
}
Loading