diff --git a/docs/README.md b/docs/README.md index e058e0c5c0..b07f334952 100644 --- a/docs/README.md +++ b/docs/README.md @@ -29,4 +29,22 @@ * When 생성한 지하철 노선을 삭제하면 * Then 해당 지하철 노선 정보는 삭제된다 * 인수 테스트의 결과가 다른 인수 테스트에 영향을 끼치지 않도록 인수 테스트를 서로 격리 시키세요. +* 인수 테스트의 재사용성과 가독성, 그리고 빠른 테스트 의도 파악을 위해 인수 테스트를 리팩터링 하세요. + +## 🚀 3단계 - 구간 추가 기능 +### 요구사항 +#### 기능 요구사항 +* 요구사항 설명에서 제공되는 요구사항을 기반으로 지하철 구간 추가 기능을 구현하세요. +* 요구사항을 정의한 인수 조건을 조출하세요. +* 인수 조건을 검증하는 인수 테스트를 작성하세요. +* 예외 케이스에 대한 검증도 포함하세요. + +#### 프로그래밍 요구사항 +* 인수 테스트 주도 개발 프로세스에 맞춰서 기능을 구현하세요. + * 요구사항 설명을 참고하여 인수 조건을 정의 + * 인수 조건을 검증하는 인수 테스트 작성 + * 인수 테스트를 충족하는 기능 구현 +* 인수 조건은 인수 테스트 메서드 상단에 주석으로 작성하세요. + * 뼈대 코드의 인수 테스트를 참고 +* 인수 테스트의 결과가 다른 인수 테스트에 영향을 끼치지 않도록 인수 테스트를 서로 격리 시키세요. * 인수 테스트의 재사용성과 가독성, 그리고 빠른 테스트 의도 파악을 위해 인수 테스트를 리팩터링 하세요. \ No newline at end of file diff --git a/src/main/java/nextstep/subway/application/LineService.java b/src/main/java/nextstep/subway/application/LineService.java index f2c6faf973..b98ff28750 100644 --- a/src/main/java/nextstep/subway/application/LineService.java +++ b/src/main/java/nextstep/subway/application/LineService.java @@ -2,10 +2,12 @@ import nextstep.subway.domain.Line; import nextstep.subway.domain.LineRepository; +import nextstep.subway.domain.Section; import nextstep.subway.domain.Station; import nextstep.subway.dto.LineRequest; import nextstep.subway.dto.LineResponse; import nextstep.subway.dto.LineUpdateRequest; +import nextstep.subway.dto.SectionRequest; import nextstep.subway.error.exception.LineNotFoundException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -29,9 +31,9 @@ public LineService(LineRepository lineRepository, StationService stationService) public LineResponse saveLine(LineRequest lineRequest) { Station upStation = stationService.findStationEntityById(lineRequest.getUpStationId()); Station downStation = stationService.findStationEntityById(lineRequest.getDownStationId()); + Line line = lineRepository.save(new Line(lineRequest.getName(), lineRequest.getColor())); + line.initSections(new Section(upStation, downStation, lineRequest.getDistance(), line)); - Line line = lineRepository.save(new Line(lineRequest.getName(), lineRequest.getColor(), - upStation, downStation, lineRequest.getDistance())); return new LineResponse(line); } @@ -62,4 +64,13 @@ public LineResponse updateLineById(Long lineId, LineUpdateRequest lineUpdateRequ line.update(lineUpdateRequest); return new LineResponse(line); } + + @Transactional + public void addSection(Long lineId, SectionRequest sectionRequest) { + Line line = getLineEntity(lineId); + Station upStation = stationService.findStationEntityById(sectionRequest.getUpStationId()); + Station downStation = stationService.findStationEntityById(sectionRequest.getDownStationId()); + Section section = new Section(upStation, downStation, sectionRequest.getDistance(), line); + line.addSection(section); + } } diff --git a/src/main/java/nextstep/subway/domain/Line.java b/src/main/java/nextstep/subway/domain/Line.java index da9a04e0ca..9ecc28a827 100644 --- a/src/main/java/nextstep/subway/domain/Line.java +++ b/src/main/java/nextstep/subway/domain/Line.java @@ -16,50 +16,39 @@ public class Line extends BaseEntity { @Column(unique = true) private String color; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "up_station_id", nullable = false) - private Station upStation; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "down_station_id", nullable = false) - private Station downStation; - - @Column(name = "distance", nullable = false) - private int distance; + @Embedded + private Sections sections; public Line() { } - public Line(String name, String color, Station upStation, Station downStation, int distance) { + public Line(String name, String color) { this.name = name; this.color = color; - this.upStation = upStation; - this.downStation = downStation; - this.distance = distance; } - public Long getId() { - return id; + public void initSections(Section section) { + sections = new Sections(this, section); } - public String getName() { - return name; + public void addSection(Section section) { + sections.add(section); } - public String getColor() { - return color; + public Sections getSections() { + return sections; } - public Station getUpStation() { - return upStation; + public Long getId() { + return id; } - public Station getDownStation() { - return downStation; + public String getName() { + return name; } - public int getDistance() { - return distance; + public String getColor() { + return color; } public void update(LineUpdateRequest lineUpdateRequest) { diff --git a/src/main/java/nextstep/subway/domain/Section.java b/src/main/java/nextstep/subway/domain/Section.java new file mode 100644 index 0000000000..ebec2beda7 --- /dev/null +++ b/src/main/java/nextstep/subway/domain/Section.java @@ -0,0 +1,116 @@ +package nextstep.subway.domain; + +import javax.persistence.*; + +@Entity +public class Section extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "up_station_id", foreignKey = @ForeignKey(name = "fk_section_to_up_station")) + private Station upStation; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "down_station_id", foreignKey = @ForeignKey(name = "fk_section_to_down_station")) + private Station downStation; + + @Column(name = "distance") + private int distance; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "line_id", nullable = false) + private Line line; + + public Section() { + } + + public Section(Station upStation, Station downStation, int distance, Line line) { + this.upStation = upStation; + this.downStation = downStation; + this.distance = distance; + this.line = line; + } + + public Station getUpStation() { + return upStation; + } + + public Station getDownStation() { + return downStation; + } + + public void arrangeFirstSection(Section newSection) { + this.downStation = newSection.upStation; + } + + public void arrangeEndSection(Section newSection) { + this.upStation = newSection.getDownStation(); + } + + public void arrangeInterSection(Section newSection) { + this.upStation = newSection.downStation; + this.distance -= newSection.distance; + } + + public boolean canAddFirstSection(Section newSection) { + if (isNotValidNewSection(newSection)) { + return false; + } + + if (!isFirstSection()) { + return false; + } + + return newSection.downStation.getId().equals(this.downStation.getId()); + } + + public boolean canAddEndSection(Section newSection) { + if (isNotValidNewSection(newSection)) { + return false; + } + + if (!isEndSection()) { + return false; + } + + return newSection.upStation.getId().equals(this.upStation.getId()); + } + + public boolean canAddInterSection(Section section) { + if (!isInterSection()) { + return false; + } + + // upStation이 같아야 inter section의 후보가 될 수 있다. + return this.upStation.getId().equals(section.upStation.getId()); + } + + private boolean isFirstSection() { + return this.upStation == null && this.downStation != null; + } + + private boolean isEndSection() { + return this.upStation != null && this.downStation == null; + } + + private boolean isInterSection() { + return !isFirstSection() && !isEndSection(); + } + + public boolean isSameSection(Section newSection) { + return isInterSection() + && this.upStation.getId().equals(newSection.getUpStation().getId()) + && this.downStation.getId().equals(newSection.getDownStation().getId()); + } + + private boolean isNotValidNewSection(Section newSection) { + // newSection의 up, down station은 모두 null이 아니어야 한다. + return newSection.getUpStation() == null || newSection.getDownStation() == null; + } + + public boolean isShorterThen(Section newSection) { + return this.distance < newSection.distance; + } +} diff --git a/src/main/java/nextstep/subway/domain/Sections.java b/src/main/java/nextstep/subway/domain/Sections.java new file mode 100644 index 0000000000..f0ea09d09f --- /dev/null +++ b/src/main/java/nextstep/subway/domain/Sections.java @@ -0,0 +1,128 @@ +package nextstep.subway.domain; + +import nextstep.subway.error.exception.SectionInvalidException; + +import javax.persistence.CascadeType; +import javax.persistence.Embeddable; +import javax.persistence.OneToMany; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Embeddable +public class Sections { + @OneToMany(mappedBy = "line", cascade = CascadeType.ALL, orphanRemoval = true) + private List
sections; + + protected Sections() { + } + + public Sections(Line line, Section section) { + this.sections = new ArrayList<>(); + sections.add(new Section(null, section.getUpStation(), 0, line)); + sections.add(new Section(section.getDownStation(), null, 0, line)); + sections.add(section); + } + + public void add(Section newSection) { + checkAlreadyExist(newSection); + if (addIfFirstSection(newSection)) { + return ; + } + if (addIfEndSection(newSection)) { + return ; + } + addIfIntersection(newSection); + } + + private boolean addIfFirstSection(Section newSection) { + Section firstSection = getFirstSection() + .orElseThrow(() -> new SectionInvalidException("first section이 없는 라인입니다.")); + if (firstSection.canAddFirstSection(newSection)) { + firstSection.arrangeFirstSection(newSection); + sections.add(newSection); + return true; + } + return false; + } + + private boolean addIfEndSection(Section newSection) { + List
sectionsInOrder = getSectionsInOrder(); + Section endSection = sectionsInOrder.get(sectionsInOrder.size() - 1); + if (endSection.canAddEndSection(newSection)) { + endSection.arrangeEndSection(newSection); + sections.add(newSection); + return true; + } + return false; + } + + private void addIfIntersection(Section newSection) { + Section upperSection = getTargetUpperSection(newSection).orElseThrow(() + -> new SectionInvalidException("상행역과 하행역 둘 다 하나도 포함되어 있지 않습니다.")); + if (upperSection.isShorterThen(newSection)) { + throw new SectionInvalidException("distance가 기존 구간의 보다 큰 길이 입니다."); + } + sections.add(newSection); + upperSection.arrangeInterSection(newSection); + } + + private Optional
getTargetUpperSection(Section newSection) { + return sections.stream() + .filter(s -> s.canAddInterSection(newSection)) + .findFirst(); + } + + public List getStationsInOrder() { + // section을 downStation을 기준으로 변경 + return getSectionsInOrder().stream() + .filter(s -> s.getDownStation() != null) + .map(Section::getDownStation) + .collect(Collectors.toList()); + } + + public List
getSectionsInOrder() { + // 출발지점 찾기 + Optional
preSection = getFirstSection(); + + // section 에 있는 station 순서대로 찾기 + List
result = new ArrayList<>(); + while (preSection.isPresent()) { + Section section = preSection.get(); + result.add(section); + preSection = sections.stream() + .filter(candidate -> isNextSection(section, candidate)) + .findFirst(); + } + return result; + } + + private Optional
getFirstSection() { + return sections.stream() + .filter(s -> s.getUpStation() == null) + .findFirst(); + } + + private boolean isNextSection(Section section, Section candidateSection) { + if (section.getDownStation() == null) { + return false; + } + + if (candidateSection.getUpStation() == null) { + return false; + } + + return candidateSection.getUpStation().getId().equals(section.getDownStation().getId()); + } + + private void checkAlreadyExist(Section section) { + if (hasSection(section)) { + throw new SectionInvalidException("이미 존재하는 section 입니다."); + } + } + + private boolean hasSection(Section newSection) { + return sections.stream().anyMatch(s -> s.isSameSection(newSection)); + } +} diff --git a/src/main/java/nextstep/subway/dto/LineResponse.java b/src/main/java/nextstep/subway/dto/LineResponse.java index 9389d715df..431123fbc1 100644 --- a/src/main/java/nextstep/subway/dto/LineResponse.java +++ b/src/main/java/nextstep/subway/dto/LineResponse.java @@ -4,6 +4,7 @@ import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; public class LineResponse { private Long id; @@ -18,10 +19,9 @@ public LineResponse(Line saved) { this.id = saved.getId(); this.name = saved.getName(); this.color = saved.getColor(); - this.stations = Arrays.asList( - new StationResponse(saved.getUpStation()), - new StationResponse(saved.getDownStation()) - ); + this.stations = saved.getSections().getStationsInOrder().stream() + .map(StationResponse::new) + .collect(Collectors.toList()); } public Long getId() { diff --git a/src/main/java/nextstep/subway/dto/SectionRequest.java b/src/main/java/nextstep/subway/dto/SectionRequest.java new file mode 100644 index 0000000000..adeb78fb8e --- /dev/null +++ b/src/main/java/nextstep/subway/dto/SectionRequest.java @@ -0,0 +1,25 @@ +package nextstep.subway.dto; + +public class SectionRequest { + private final Long upStationId; + private final Long downStationId; + private final int distance; + + public SectionRequest(Long upStationId, Long downStationId, int distance) { + this.upStationId = upStationId; + this.downStationId = downStationId; + this.distance = distance; + } + + public Long getUpStationId() { + return upStationId; + } + + public Long getDownStationId() { + return downStationId; + } + + public int getDistance() { + return distance; + } +} diff --git a/src/main/java/nextstep/subway/error/SubwayError.java b/src/main/java/nextstep/subway/error/SubwayError.java index 5929a61a4c..7a807f4978 100644 --- a/src/main/java/nextstep/subway/error/SubwayError.java +++ b/src/main/java/nextstep/subway/error/SubwayError.java @@ -4,6 +4,7 @@ public enum SubwayError { NOT_DEFINED(0), LINE_NOT_FOUND(1004), STATION_NOT_FOUND(2004), + SECTION_INVALID_REQUEST(3001), ; SubwayError(int code) { diff --git a/src/main/java/nextstep/subway/error/exception/SectionInvalidException.java b/src/main/java/nextstep/subway/error/exception/SectionInvalidException.java new file mode 100644 index 0000000000..be58fc9d89 --- /dev/null +++ b/src/main/java/nextstep/subway/error/exception/SectionInvalidException.java @@ -0,0 +1,12 @@ +package nextstep.subway.error.exception; + +import nextstep.subway.error.SubwayError; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.BAD_REQUEST) +public class SectionInvalidException extends SubWayApiException { + public SectionInvalidException(String message) { + super(SubwayError.SECTION_INVALID_REQUEST, message); + } +} diff --git a/src/main/java/nextstep/subway/exception/LineNotFoundException.java b/src/main/java/nextstep/subway/exception/LineNotFoundException.java deleted file mode 100644 index adbe1f27eb..0000000000 --- a/src/main/java/nextstep/subway/exception/LineNotFoundException.java +++ /dev/null @@ -1,12 +0,0 @@ -package nextstep.subway.exception; - -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - -@ResponseStatus(code = HttpStatus.NOT_FOUND) -public class LineNotFoundException extends Exception { - - public LineNotFoundException(Long lineId) { - super(String.format("LineId(%d)을 가진 Line을 찾을 수 없습니다.", lineId)); - } -} diff --git a/src/main/java/nextstep/subway/exception/StationNotFoundException.java b/src/main/java/nextstep/subway/exception/StationNotFoundException.java deleted file mode 100644 index 57b4b86eef..0000000000 --- a/src/main/java/nextstep/subway/exception/StationNotFoundException.java +++ /dev/null @@ -1,12 +0,0 @@ -package nextstep.subway.exception; - -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - -@ResponseStatus(code = HttpStatus.NOT_FOUND) -public class StationNotFoundException extends Exception { - - public StationNotFoundException(Long stationId) { - super(String.format("stationId(%d)을 가진 Station 찾을 수 없습니다.", stationId)); - } -} diff --git a/src/main/java/nextstep/subway/ui/LineController.java b/src/main/java/nextstep/subway/ui/LineController.java index 63f1a3339f..3e3124cffe 100644 --- a/src/main/java/nextstep/subway/ui/LineController.java +++ b/src/main/java/nextstep/subway/ui/LineController.java @@ -4,7 +4,6 @@ import nextstep.subway.dto.*; import nextstep.subway.error.ApiExceptionResponse; import nextstep.subway.error.SubwayError; -import nextstep.subway.error.exception.StationNotFoundException; import nextstep.subway.error.exception.SubWayApiException; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -23,12 +22,18 @@ public LineController(LineService lineService) { } @PostMapping - public ResponseEntity createLine(@RequestBody LineRequest lineRequest) - throws StationNotFoundException { + public ResponseEntity createLine(@RequestBody LineRequest lineRequest) { LineResponse line = lineService.saveLine(lineRequest); return ResponseEntity.created(URI.create("/lines/" + line.getId())).body(line); } + @PostMapping("/{id}/sections") + public ResponseEntity addSection(@PathVariable("id") Long lineId, + @RequestBody SectionRequest sectionRequest) { + lineService.addSection(lineId, sectionRequest); + return ResponseEntity.ok().build(); + } + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity> showLines() { return ResponseEntity.ok().body(lineService.findAllLines()); diff --git a/src/test/java/nextstep/subway/line/SectionAcceptanceTest.java b/src/test/java/nextstep/subway/line/SectionAcceptanceTest.java new file mode 100644 index 0000000000..7c29647c32 --- /dev/null +++ b/src/test/java/nextstep/subway/line/SectionAcceptanceTest.java @@ -0,0 +1,183 @@ +package nextstep.subway.line; + +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import nextstep.subway.AcceptanceTest; +import nextstep.subway.dto.LineRequest; +import nextstep.subway.dto.LineResponse; +import nextstep.subway.dto.SectionRequest; +import nextstep.subway.dto.StationResponse; +import nextstep.subway.station.StationTestHelper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +@DisplayName("지하철노선 추가 관련 기능") +public class SectionAcceptanceTest extends AcceptanceTest { + + StationResponse 강남역; + StationResponse 광교역; + LineResponse 신분당선; + int 신분당선_distance = 50; + + @BeforeEach + @Override + public void setUp() { + super.setUp(); + + 강남역 = StationTestHelper.지하철역_생성("강남역").as(StationResponse.class); + 광교역 = StationTestHelper.지하철역_생성("광교역").as(StationResponse.class); + 신분당선 = LineTestHelper.지하철_노선_생성(new LineRequest("신분당선", "red", + 강남역.getId(), 광교역.getId(), 신분당선_distance)).as(LineResponse.class); + } + + /** + * Given 새로운 지하철 역을 등록한다. + * When 지하철 노선에 역과 역 사이에 새로운 역을 추가한다. + * Then 지하철 노선에 새로운 지하철역이 등록된다. + */ + @DisplayName("역 사이에 새로운 역을 등록할 경우") + @Test + void 새로운_중간역_추가() { + // given + StationResponse 정자역 = StationTestHelper.지하철역_생성("정자역").as(StationResponse.class); + + // when + SectionTestHelper.지하철에_노선_지하철역_등록(신분당선.getId(), new SectionRequest(강남역.getId(), 정자역.getId(), 10)); + + // then + LineResponse response = LineTestHelper.지하철_노선_ID로_조회(신분당선.getId()).as(LineResponse.class); + List stationNameList = response.getStations().stream().map(StationResponse::getName).collect(Collectors.toList()); + assertAll( + () -> assertThat(stationNameList).hasSize(3), + () -> assertThat(stationNameList).containsExactly("강남역", "정자역", "광교역") + ); + } + + /** + * Given 새로운 지하철 역을 등록한다. + * When 지하철 노선에 새로운 상행역을 추가한다. + * Then 지하철 노선에 새로운 지하철역이 등록된다. + */ + @DisplayName("새로운 상행 종점역을 등록할 경우") + @Test + void 새로운_상행역_추가() { + // given + StationResponse 신사역 = StationTestHelper.지하철역_생성("신사역").as(StationResponse.class); + + // when + SectionTestHelper.지하철에_노선_지하철역_등록(신분당선.getId(), new SectionRequest(신사역.getId(), 강남역.getId(), 0)); + + // then + LineResponse response = LineTestHelper.지하철_노선_ID로_조회(신분당선.getId()).as(LineResponse.class); + List stationNameList = response.getStations().stream().map(StationResponse::getName).collect(Collectors.toList()); + assertAll( + () -> assertThat(stationNameList).hasSize(3), + () -> assertThat(stationNameList).containsExactly("신사역", "강남역", "광교역") + ); + } + + /** + * Given 새로운 지하철 역을 등록한다. + * When 지하철 노선에 새로운 하행역을 추가한다. + * Then 지하철 노선에 새로운 지하철역이 등록된다. + */ + @DisplayName("새로운 하행 종점역을 등록할 경우") + @Test + void 새로운_하행역_추가() { + // given + StationResponse 신광교역 = StationTestHelper.지하철역_생성("신광교역").as(StationResponse.class); + + // when + SectionTestHelper.지하철에_노선_지하철역_등록(신분당선.getId(), new SectionRequest(광교역.getId(), 신광교역.getId(), 0)); + + // then + LineResponse response = LineTestHelper.지하철_노선_ID로_조회(신분당선.getId()).as(LineResponse.class); + List stationNameList = response.getStations().stream().map(StationResponse::getName).collect(Collectors.toList()); + assertAll( + () -> assertThat(stationNameList).hasSize(3), + () -> assertThat(stationNameList).containsExactly("강남역", "광교역", "신광교역") + ); + } + + ////// + /** + * Given 새로운 지하철 역을 등록한다. + * When 지하철 노선에 역과 역 사이에 새로운 역을 추가한다. 하지만 distance가 기존의 역 보다 큰 값 + * Then 지하철 노선에 새로운 지하철역이 등록되지 않는다. + */ + @DisplayName("역 사이에 새로운 역을 등록할 경우에 distance가 기존보다 큰 경우") + @Test + void 새로운_중간역_추가_중_실패() { + // given + StationResponse 정자역 = StationTestHelper.지하철역_생성("정자역").as(StationResponse.class); + + // when + ExtractableResponse addSectionResponse = SectionTestHelper.지하철에_노선_지하철역_등록(신분당선.getId(), new SectionRequest(강남역.getId(), 정자역.getId(), 신분당선_distance + 1)); + + // then + LineResponse lineResponse = LineTestHelper.지하철_노선_ID로_조회(신분당선.getId()).as(LineResponse.class); + List stationNameList = lineResponse.getStations().stream().map(StationResponse::getName).collect(Collectors.toList()); + assertAll( + () -> assertThat(addSectionResponse.statusCode()).isNotEqualTo(200), + () -> assertThat(stationNameList).hasSize(2), + () -> assertThat(stationNameList).containsExactly("강남역", "광교역") + ); + } + + /** + * Given 새로운 지하철 역을 등록한다. + * When 지하철 노선에 역과 역 사이에 새로운 역을 추가하고, 다시 추가를 시도 + * Then 지하철 노선에 새로운 지하철역이 등록되지 않는다. + */ + @DisplayName("역 사이에 새로운 역을 등록할 경우에 이미 등록된 구간일 경우 실패") + @Test + void 이미_등록된_중간역_추가_중_실패() { + // given + StationResponse 정자역 = StationTestHelper.지하철역_생성("정자역").as(StationResponse.class); + SectionTestHelper.지하철에_노선_지하철역_등록(신분당선.getId(), new SectionRequest(강남역.getId(), 정자역.getId(), 10)); + + // when + ExtractableResponse addSectionResponse = SectionTestHelper.지하철에_노선_지하철역_등록(신분당선.getId(), new SectionRequest(강남역.getId(), 정자역.getId(), 20)); + + // then + LineResponse lineResponse = LineTestHelper.지하철_노선_ID로_조회(신분당선.getId()).as(LineResponse.class); + List stationNameList = lineResponse.getStations().stream().map(StationResponse::getName).collect(Collectors.toList()); + assertAll( + () -> assertThat(addSectionResponse.statusCode()).isNotEqualTo(200), + () -> assertThat(stationNameList).hasSize(3), + () -> assertThat(stationNameList).containsExactly("강남역", "정자역", "광교역") + ); + } + + /** + * Given 새로운 지하철 역 2개 등록한다. + * When 지하철 노선에 역과 역 사이에 새로운 역을 추가하되, 상행과 하행은 기존에 포함되지 않은 역 + * Then 지하철 노선에 새로운 지하철역이 등록되지 않는다. + */ + @DisplayName("새로운 역을 등록할 경우에 상행/하행 둘 다 포함되어 있지 않을 경우 실패") + @Test + void 상행역_하행역_둘_중_하나도_포함이_되지_않는_역_추가시_실패() { + // given + StationResponse 정자역 = StationTestHelper.지하철역_생성("정자역").as(StationResponse.class); + StationResponse 미금역 = StationTestHelper.지하철역_생성("미금역").as(StationResponse.class); + + // when + ExtractableResponse addSectionResponse = SectionTestHelper.지하철에_노선_지하철역_등록(신분당선.getId(), new SectionRequest(정자역.getId(), 미금역.getId(), 20)); + + // then + LineResponse lineResponse = LineTestHelper.지하철_노선_ID로_조회(신분당선.getId()).as(LineResponse.class); + List stationNameList = lineResponse.getStations().stream().map(StationResponse::getName).collect(Collectors.toList()); + assertAll( + () -> assertThat(addSectionResponse.statusCode()).isNotEqualTo(200), + () -> assertThat(stationNameList).hasSize(2), + () -> assertThat(stationNameList).containsExactly("강남역", "광교역") + ); + } +} diff --git a/src/test/java/nextstep/subway/line/SectionTestHelper.java b/src/test/java/nextstep/subway/line/SectionTestHelper.java new file mode 100644 index 0000000000..ad6ae38226 --- /dev/null +++ b/src/test/java/nextstep/subway/line/SectionTestHelper.java @@ -0,0 +1,19 @@ +package nextstep.subway.line; + +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import nextstep.subway.dto.SectionRequest; +import org.springframework.http.MediaType; + +public class SectionTestHelper { + + static ExtractableResponse 지하철에_노선_지하철역_등록(Long lineId, SectionRequest sectionRequest) { + return RestAssured.given().log().all() + .body(sectionRequest) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when().post("/lines/{lineId}/sections", lineId) + .then().log().all() + .extract(); + } +} diff --git a/src/test/java/nextstep/subway/station/StationAcceptanceTest.java b/src/test/java/nextstep/subway/station/StationAcceptanceTest.java index 5de2fc1be6..06ad225b30 100644 --- a/src/test/java/nextstep/subway/station/StationAcceptanceTest.java +++ b/src/test/java/nextstep/subway/station/StationAcceptanceTest.java @@ -3,6 +3,7 @@ import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; import nextstep.subway.AcceptanceTest; +import nextstep.subway.dto.StationResponse; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.http.HttpStatus;