diff --git a/.gitignore b/.gitignore index b44bc4d91..e8d7624b2 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,7 @@ out/ /.nb-gradle/ ### VS Code ### -.vscode/ \ No newline at end of file +.vscode/ + +### http ### +http-request-log.http \ No newline at end of file diff --git a/.idea/httpRequests/http-requests-log.http b/.idea/httpRequests/http-requests-log.http new file mode 100644 index 000000000..0d47153a5 --- /dev/null +++ b/.idea/httpRequests/http-requests-log.http @@ -0,0 +1,695 @@ +PATCH http://localhost:8080/lines/1/stations/3/endpoint +Content-Type: application/json +Content-Length: 47 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "endpointType": "sadf", + "distance": "1" +} + +<> 2023-05-22T001036.400.txt + +### + +PATCH http://localhost:8080/lines/1/stations/3/endpoint +Content-Type: application/json +Content-Length: 45 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "endpointType": "up", + "distance": "1" +} + +<> 2023-05-22T001030.400.txt + +### + +PATCH http://localhost:8080/lines/1/stations/3/endpoint +Content-Type: application/json +Content-Length: 45 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "endpointType": "up", + "distance": "1" +} + +<> 2023-05-21T225659.400.txt + +### + +PATCH http://localhost:8080/lines/1/stations/3/endpoint +Content-Type: application/json +Content-Length: 48 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "endpointType": "uasdf", + "distance": "1" +} + +<> 2023-05-21T225654.400.txt + +### + +PATCH http://localhost:8080/lines/1/stations/3/endpoint +Content-Type: application/json +Content-Length: 55 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "endpointType": "ㅁㄴㅇㄹ", + "distance": "1" +} + +### + +PATCH http://localhost:8080/lines/1/stations/3/endpoint +Content-Type: application/json +Content-Length: 45 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "endpointType": "up", + "distance": "1" +} + +<> 2023-05-21T224825.400.txt + +### + +PATCH http://localhost:8080/lines/1/stations/3/endpoint +Content-Type: application/json +Content-Length: 55 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "endpointType": "ㅁㄴㅇㄹ", + "distance": "1" +} + +### + +PATCH http://localhost:8080/lines/1/stations/3/endpoint +Content-Type: application/json +Content-Length: 45 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "endpointType": "up", + "distance": "1" +} + +### + +PATCH http://localhost:8080/lines/1/stations/3/endpoint +Content-Type: application/json +Content-Length: 45 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "endpointType": "up", + "distance": "1" +} + +### + +PATCH http://localhost:8080/lines/1/stations/1/init +Content-Type: application/json +Content-Length: 45 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "nextStationId": "2", + "distance": "3" +} + +### + +PATCH http://localhost:8080/lines/1/stations/3/endpoint +Content-Type: application/json +Content-Length: 45 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "endpointType": "up", + "distance": "1" +} + +### + +PATCH http://localhost:8080/lines/1/stations/3/endpoint +Content-Type: application/json +Content-Length: 47 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "endpointType": "asdf", + "distance": "1" +} + +### + +PATCH http://localhost:8080/lines/1/stations/3/endpoint +Content-Type: application/json +Content-Length: 47 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "endpointType": "qwer", + "distance": "1" +} + +### + +PATCH http://localhost:8080/lines/1/stations/3/endpoint +Content-Type: application/json +Content-Length: 49 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "connectionType": "qwer", + "distance": "1" +} + +<> 2023-05-21T220905.400.txt + +### + +PATCH http://localhost:8080/lines/1/stations/3/endpoint +Content-Type: application/json +Content-Length: 49 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "connectionType": "1234", + "distance": "1" +} + +<> 2023-05-21T220801.400.txt + +### + +PATCH http://localhost:8080/lines/1/stations/3/endpoint +Content-Type: application/json +Content-Length: 49 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "connectionType": "1234", + "distance": "1" +} + +<> 2023-05-21T220736.400.txt + +### + +PATCH http://localhost:8080/lines/1/stations/1/init +Content-Type: application/json +Content-Length: 45 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "nextStationId": "2", + "distance": "3" +} + +### + +PATCH http://localhost:8080/lines/1/stations/1/init +Content-Type: application/json +Content-Length: 45 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "nextStationId": "2", + "distance": "3" +} + +<> 2023-05-21T220621.400.txt + +### + +POST http://localhost:8080/lines +Content-Type: application/json +Content-Length: 43 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "name": "2호선", + "color": "green" +} + +### + +PATCH http://localhost:8080/lines/1/stations/4/endpoint +Content-Type: application/json +Content-Length: 49 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "connectionType": "down", + "distance": "1" +} + +### + +PATCH http://localhost:8080/lines/1/stations/3/endpoint +Content-Type: application/json +Content-Length: 47 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "connectionType": "up", + "distance": "1" +} + +<> 2023-05-20T192445.400.txt + +### + +POST http://localhost:8080/lines +Content-Type: application/json +Content-Length: 43 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "name": "2호선", + "color": "green" +} + +### + +PATCH http://localhost:8080/lines/1/stations/3/endpoint +Content-Type: application/json +Content-Length: 47 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "connectionType": "UP", + "distance": "1" +} + +### + +PATCH http://localhost:8080/lines/1/stations/3/endpoint +Content-Type: application/json +Content-Length: 47 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "connectionType": "up", + "distance": "1" +} + +### + +PATCH http://localhost:8080/lines/1/stations/3/up +Content-Type: application/json +Content-Length: 21 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "distance": "1" +} + +<> 2023-05-20T191841.404.json + +### + +PATCH http://localhost:8080/lines/1/stations/1/init +Content-Type: application/json +Content-Length: 45 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "nextStationId": "2", + "distance": "3" +} + +### + +POST http://localhost:8080/stations +Content-Type: application/json +Content-Length: 31 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "name": "건대입구역" +} + +### + +POST http://localhost:8080/stations +Content-Type: application/json +Content-Length: 25 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "name": "선릉역" +} + +### + +POST http://localhost:8080/stations +Content-Type: application/json +Content-Length: 25 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "name": "잠실역" +} + +### + +POST http://localhost:8080/stations +Content-Type: application/json +Content-Length: 25 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "name": "강남역" +} + +### + +POST http://localhost:8080/lines +Content-Type: application/json +Content-Length: 43 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "name": "2호선", + "color": "green" +} + +<> 2023-05-20T191829.201.json + +### + +PATCH http://localhost:8080/lines/12/stations/5/init +Content-Type: application/json +Content-Length: 45 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "nextStationId": "6", + "distance": "3" +} + +### + +PATCH http://localhost:8080/lines/1/stations/5/init +Content-Type: application/json +Content-Length: 45 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "nextStationId": "6", + "distance": "3" +} + +### + +POST http://localhost:8080/stations +Content-Type: application/json +Content-Length: 25 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "name": "잠실역" +} + +### + +POST http://localhost:8080/stations +Content-Type: application/json +Content-Length: 25 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "name": "강남역" +} + +### + +POST http://localhost:8080/lines +Content-Type: application/json +Content-Length: 43 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "name": "2호선", + "color": "green" +} + +<> 2023-05-20T191711.201.json + +### + +PATCH http://localhost:8080/lines/1/stations/1/init +Content-Type: application/json +Content-Length: 45 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "nextStationId": "2", + "distance": "3" +} + +<> 2023-05-20T191601.400.txt + +### + +POST http://localhost:8080/stations +Content-Type: application/json +Content-Length: 25 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "name": "잠실역" +} + +### + +POST http://localhost:8080/stations +Content-Type: application/json +Content-Length: 25 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "name": "강남역" +} + +### + +PATCH http://localhost:8080/lines/1/stations/1/init +Content-Type: application/json +Content-Length: 45 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "nextStationId": "2", + "distance": "3" +} + +<> 2023-05-20T191550.400.txt + +### + +PATCH http://localhost:8080/lines/1/stations/1?type=init +Content-Type: application/json +Content-Length: 45 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "nextStationId": "2", + "distance": "3" +} + +### + +POST http://localhost:8080/lines +Content-Type: application/json +Content-Length: 43 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "name": "2호선", + "color": "green" +} + +### + +POST http://localhost:8080/stations +Content-Type: application/json +Content-Length: 16 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "name": "" +} + +<> 2023-05-18T014819.400.txt + +### + +POST http://localhost:8080/stations +Content-Type: application/json +Content-Length: 0 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +### + +POST http://localhost:8080/stations +Content-Type: application/json +Content-Length: 0 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +### + +DELETE http://localhost:8080/lines/1/stations/2 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +### + +DELETE http://localhost:8080/lines/1/stations/3 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +### + +PATCH http://localhost:8080/lines/1/stations/5?type=down +Content-Type: application/json +Content-Length: 21 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "distance": "1" +} + +### + +PATCH http://localhost:8080/lines/1/stations/4?type=down +Content-Type: application/json +Content-Length: 21 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "distance": "1" +} + +### + +PATCH http://localhost:8080/lines/1/stations/3?type=down +Content-Type: application/json +Content-Length: 21 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6) +Accept-Encoding: br,deflate,gzip,x-gzip + +{ + "distance": "1" +} + +### + diff --git a/README.md b/README.md index 57e063f77..8b1342efe 100644 --- a/README.md +++ b/README.md @@ -17,53 +17,62 @@ - [X] 다음 역과 이전 역 사이에 삽입 가능한 거리인지 검증한다 - [X] 다음 역과 이전 역의 연결을 변경한다 - [X] 다음 역과 이전 역 까지의 거리를 변경한다 -- [ ] 역 제거 - - [ ] 상행 종점인 경우 - - [ ] 다음 역의 이전 역을 삭제한다 - - [ ] 하행 종점이 경우 - - [ ] 이전 역의 다음 역을 삭제 한다 - - [ ] 중간 역인 경우 - - [ ] 이전 역과 다음 역을 연결한다 - - [ ] 이전 역과 다음 역 사이의 거리를 변경한다 - - [ ] 마지막 두 역인 경우 - - [ ] 두 역을 모두 삭제한다 -- [ ] 노선 조회 개선 - - [ ] 특정 노선의 역 목록을 조회한다 - - [ ] 상행 종점을 찾는다 - - [ ] 상행 종점부터 다음 역을 탐색한다 - - [ ] 하행 종점에 도달하면 종료한다 -- [ ] 노선 목록 조회 개선 - - [ ] 전체 노선의 역 목록을 조회한다 - - [ ] 상행 종점을 찾는다 - - [ ] 상행 종점부터 다음 역을 탐색한다 - - [ ] 하행 종점에 도달하면 종료한다 +- [X] 역 제거 + - [X] 상행 종점인 경우 + - [X] 다음 역의 이전 역을 삭제한다 + - [X] 하행 종점이 경우 + - [X] 이전 역의 다음 역을 삭제 한다 + - [X] 중간 역인 경우 + - [X] 이전 역과 다음 역을 연결한다 + - [X] 이전 역과 다음 역 사이의 거리를 변경한다 + - [X] 마지막 두 역인 경우 + - [X] 두 역을 모두 삭제한다 +- [X] 노선 조회 개선 + - [X] 특정 노선의 역 목록을 조회한다 + - [X] 상행 종점을 찾는다 + - [X] 상행 종점부터 다음 역을 탐색한다 + - [X] 하행 종점에 도달하면 종료한다 +- [X] 노선 목록 조회 개선 + - [X] 전체 노선의 역 목록을 조회한다 + - [X] 상행 종점을 찾는다 + - [X] 상행 종점부터 다음 역을 탐색한다 + - [X] 하행 종점에 도달하면 종료한다 + +## 🎯 추가된 기능 요구사항 + +- [X] 경로 조회 API 구현 +- [X] 요금 조회 기능 추가 + - [X] 10km 까지 기본 요금 1250원 + - [X] 11km ~ 50km 까지 5km 마다 100원 추가 (11km는 1350원) + - [X] 51km ~ 8km 마다 100원 추가 + ## 🛠️설계 ### DB -- Line +**station** : -| column | type | | -|----------------|--------------|--------------------| -| id | BIGINT | PK, AUTO INCREMENT | -| name | VARCHAR(255) | UNIQUE | -| color | VARCHAR(20) | | -| up_endpoint_id | BIGINT | | +| Column | Type | Constraints | +| ------------- | --------------------- |------------------| +| station_id | BIGINT AUTO_INCREMENT | NOT NULL, PK | +| name | VARCHAR(255) | NOT NULL, UNIQUE | -- Station +**line** : -| column | type | | -|--------|--------------|--------------------| -| id | BIGINT | PK, AUTO INCREMENT | -| name | VARCHAR(255) | UNIQUE | +| Column | Type | Constraints | +| ------------- | --------------------- |------------------| +| line_id | BIGINT AUTO_INCREMENT | NOT NULL, PK | +| name | VARCHAR(255) | NOT NULL, UNIQUE | +| color | VARCHAR(20) | NOT NULL | -- Section +**section** : -| column | type | | -|--------------|--------|--------------------| -| id | BIGINT | PK, AUTO INCREMENT | -| departure_id | BIGINT | NOT NULL | -| arrival_id | BIGINT | NOT NULL | -| distance | INT | NOT NULL | -| line_id | BIGINT | NOT NULL | +| Column | Type | Constraints | +| -------------- | --------------------- |--------------| +| section_id | BIGINT AUTO_INCREMENT | NOT NULL, PK | +| up_station_id | BIGINT | NOT NULL | +| down_station_id| BIGINT | NOT NULL | +| distance | INT | NOT NULL | +| line_id | BIGINT | NOT NULL | +| list_order | INT | NOT NULL | diff --git a/build.gradle b/build.gradle index 68d8f2558..2cf3967a9 100644 --- a/build.gradle +++ b/build.gradle @@ -1,27 +1,30 @@ plugins { - id 'java' - id 'org.springframework.boot' version '2.7.9' - id 'io.spring.dependency-management' version '1.0.15.RELEASE' + id 'java' + id 'org.springframework.boot' version '2.7.9' + id 'io.spring.dependency-management' version '1.0.15.RELEASE' } sourceCompatibility = '11' repositories { - mavenCentral() + mavenCentral() } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springframework.boot:spring-boot-starter-jdbc' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-jdbc' + implementation 'org.jgrapht:jgrapht-core:1.0.1' - implementation 'net.rakugakibox.spring.boot:logback-access-spring-boot-starter:2.7.1' + implementation 'net.rakugakibox.spring.boot:logback-access-spring-boot-starter:2.7.1' + implementation 'org.springframework.boot:spring-boot-starter-validation' - testImplementation 'io.rest-assured:rest-assured:4.4.0' - testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'io.rest-assured:rest-assured:4.4.0' + testImplementation 'org.springframework.boot:spring-boot-starter-test' - runtimeOnly 'com.h2database:h2' + runtimeOnly 'mysql:mysql-connector-java:8.0.28' + runtimeOnly 'com.h2database:h2' } test { - useJUnitPlatform() + useJUnitPlatform() } \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 000000000..00debe968 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,19 @@ +version: "3.9" +services: + db: + image: mysql:8.0.28 + platform: linux/x86_64 + restart: always + ports: + - "13306:3306" + environment: + MYSQL_ROOT_PASSWORD: maco + MYSQL_DATABASE: subway + MYSQL_USER: maco + MYSQL_PASSWORD: maco + TZ: Asia/Seoul + volumes: + - ./db/mysql/data:/var/lib/mysql + - ./db/mysql/config:/etc/mysql/conf.d + - ./db/mysql/init:/docker-entrypoint-initdb.d + command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci diff --git a/http/Line.http b/http/Line.http index 100c2db47..a660eba14 100644 --- a/http/Line.http +++ b/http/Line.http @@ -9,6 +9,15 @@ Content-Type: application/json "color": "green" } +### +POST http://localhost:8080/lines +Content-Type: application/json + +{ + "name": "3호선", + "color": "green" +} + ######################### ### diff --git a/http/LineStations.http b/http/LineStations.http new file mode 100644 index 000000000..fa855ccd7 --- /dev/null +++ b/http/LineStations.http @@ -0,0 +1,46 @@ +### 초기 두 역 등록 +PATCH http://localhost:8080/lines/1/stations/1/init +Content-Type: application/json + +{ + "nextStationId": "2", + "distance": "3" +} + +### 상행 종점 등록 +PATCH http://localhost:8080/lines/1/stations/3/endpoint +Content-Type: application/json + +{ + "endpointType": "up", + "distance": "1" +} + + +### 하행 종점 등록 +PATCH http://localhost:8080/lines/1/stations/4/endpoint +Content-Type: application/json + +{ + "endpointType": "down", + "distance": "1" +} + +### 중간역 등록 +PATCH http://localhost:8080/lines/1/stations/3/mid +Content-Type: application/json + +{ + "distance": "2", + "prevStationId": "1" +} + + +### 라인에 등록된 역 정보 삭제 +DELETE http://localhost:8080/lines/1/stations/2 + +### 라인에 해당하는 모든 역 조회 +GET http://localhost:8080/lines/4/stations + +### 모든 역 조회 +GET http://localhost:8080/lines/stations \ No newline at end of file diff --git a/http/Station.http b/http/Station.http index 80bbf53d3..c914512b6 100644 --- a/http/Station.http +++ b/http/Station.http @@ -1,98 +1,47 @@ - -### 한 노선에서 첫 등록인 경우 (역이 두개 입력), type: 'init' -POST http://localhost:8080/stations/ +### +POST http://localhost:8080/stations Content-Type: application/json -[ - { - "name": "잠실역", - "create_type": "INIT", - "line": "2호선", - "up_endpoint": true, - "down_endpoint": false, - "previous_station": null, - "previous_distance": null, - "next_station": "잠실나루역", - "next_distance": 1 - }, - { - "name": "잠실나루역", - "create_type": "INIT", - "line": "2호선", - "up_endpoint": false, - "down_endpoint": true, - "previous_station": "잠실역", - "previous_distance": 1, - "next_station": null, - "next_distance": null - } -] +{ + "name": "강남역" +} -### 상행 종점인 경우 -POST http://localhost:8080/stations/ +### +POST http://localhost:8080/stations Content-Type: application/json -[ - { - "name": "잠실역", - "create_type": "UP", - "line": "2호선", - "up_endpoint": true, - "down_endpoint": false, - "previous_station": null, - "previous_distance": null, - "next_station": "잠실나루역", - "next_distance": 1 - } -] +{ + "name": "잠실역" +} -### 중간 역인 경우 -POST http://localhost:8080/stations/ +### +POST http://localhost:8080/stations Content-Type: application/json -[ - { - "name": "잠실역", - "create_type": "MID", - "line": "2호선", - "up_endpoint": false, - "down_endpoint": false, - "previous_station": "잠실새내역", - "previous_distance": 1, - "next_station": "잠실나루역", - "next_distance": 1 - } -] - +{ + "name": "선릉역" +} -### 하행 종점인 경우 -POST http://localhost:8080/stations/ +### +POST http://localhost:8080/stations Content-Type: application/json -[ - { - "name": "잠실역", - "create_type": "DOWN", - "line": "2호선", - "up_endpoint": false, - "down_endpoint": true, - "previous_station": "잠실새내역", - "previous_distance": 1, - "next_station": null, - "next_distance": null - } -] +{ + "name": "건대입구역" +} -######################### - ### -DELETE http://localhost:8080/stations/{id} +POST http://localhost:8080/stations +Content-Type: application/json -### -GET http://localhost:8080/stations/ +{ + "name": "구의역" +} + + +### 역, 라인에 등록된 역 정보 삭제 +DELETE http://localhost:8080/stations/2 -### -GET http://localhost:8080/stations/lines/{id} diff --git a/http/test.http b/http/test.http deleted file mode 100644 index 585be874b..000000000 --- a/http/test.http +++ /dev/null @@ -1,39 +0,0 @@ - -### -POST http://localhost:8080/lines -Content-Type: application/json - -{ - "name": "2호선", - "color": "green" -} - - -### 한 노선에서 첫 등록인 경우 (역이 두개 입력), type: 'init' -POST http://localhost:8080/stations/ -Content-Type: application/json - -[ - { - "name": "잠실역", - "create_type": "INIT", - "line": "2호선", - "up_endpoint": true, - "down_endpoint": false, - "previous_station": null, - "previous_distance": null, - "next_station": "잠실나루역", - "next_distance": 1 - }, - { - "name": "잠실나루역", - "create_type": "INIT", - "line": "2호선", - "up_endpoint": false, - "down_endpoint": true, - "previous_station": "잠실역", - "previous_distance": 1, - "next_station": null, - "next_distance": null - } -] diff --git a/src/main/java/subway/SubwayApplication.java b/src/main/java/subway/SubwayApplication.java index 5780e8fb1..9fb89735a 100644 --- a/src/main/java/subway/SubwayApplication.java +++ b/src/main/java/subway/SubwayApplication.java @@ -6,7 +6,7 @@ @SpringBootApplication public class SubwayApplication { - public static void main(String[] args) { + public static void main(final String[] args) { SpringApplication.run(SubwayApplication.class, args); } diff --git a/src/main/java/subway/application/LineService.java b/src/main/java/subway/application/LineService.java index 1a7fb332a..052a842d1 100644 --- a/src/main/java/subway/application/LineService.java +++ b/src/main/java/subway/application/LineService.java @@ -1,60 +1,68 @@ package subway.application; import org.springframework.stereotype.Service; -import subway.dao.LineDao; -import subway.dao.entity.LineEntity; +import org.springframework.transaction.annotation.Transactional; import subway.domain.Line; -import subway.dto.LineRequest; -import subway.dto.LineResponse; +import subway.domain.LineRepository; +import subway.domain.Lines; +import subway.dto.request.LineRequest; +import subway.dto.response.LineResponse; import java.util.List; import java.util.stream.Collectors; +@Transactional @Service public class LineService { - private final LineDao lineDao; - public LineService(final LineDao lineDao) { - this.lineDao = lineDao; - } + private final LineRepository lineRepository; - public LineResponse saveLine(final LineRequest request) { - final Line persistLine = lineDao.insert(new LineEntity(request.getName(), request.getColor())); - return LineResponse.of(persistLine); + public LineService(final LineRepository lineRepository) { + this.lineRepository = lineRepository; } - public List findLineResponses() { - final List persistLines = findLines(); - return persistLines.stream() - .map(LineResponse::of) - .collect(Collectors.toList()); + public LineResponse createLine(final LineRequest request) { + final Line line = request.toLine(); + final Line savedLine = lineRepository.save(line); + + return LineResponse.of(savedLine); } - public List findLines() { - return lineDao.findAll().stream() - .map(LineEntity::toLine) - .collect(Collectors.toList()); + public void save(final Line line) { + lineRepository.save(line); } - public LineResponse findLineResponseById(final Long id) { - final Line persistLine = findLineById(id); - return LineResponse.of(persistLine); + public void updateLine(final Long id, final LineRequest lineUpdateRequest) { + final Line line = findById(id); + + line.update(lineUpdateRequest.getName(), lineUpdateRequest.getColor()); + + lineRepository.save(line); } - public Line findLineById(final Long id) { - return lineDao.findById(id); + public void deleteLineById(final Long id) { + lineRepository.findById(id).ifPresent(lineRepository::delete); } - public Line findLineByName(final String name) { - return lineDao.findByName(name); + @Transactional(readOnly = true) + public List findLineResponses() { + return lineRepository.findAll().getLines().stream() + .map(LineResponse::of) + .collect(Collectors.toList()); } - public void updateLine(final Long id, final LineRequest lineUpdateRequest) { - lineDao.update(id, new Line(id, lineUpdateRequest.getName(), lineUpdateRequest.getColor())); + public LineResponse findLineResponseById(final Long id) { + return LineResponse.of(findById(id)); } - public void deleteLineById(final Long id) { - lineDao.deleteById(id); + @Transactional(readOnly = true) + public Line findById(final Long id) { + return lineRepository.findById(id) + .orElseThrow(() -> new IllegalArgumentException("id에 해당하는 라인이 존재하지 않습니다.")); } + @Transactional(readOnly = true) + public Lines findAll() { + return lineRepository.findAll(); + } } diff --git a/src/main/java/subway/application/LineStationService.java b/src/main/java/subway/application/LineStationService.java new file mode 100644 index 000000000..b88d733c6 --- /dev/null +++ b/src/main/java/subway/application/LineStationService.java @@ -0,0 +1,114 @@ +package subway.application; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import subway.domain.Line; +import subway.domain.Lines; +import subway.domain.Station; +import subway.dto.response.LineStationResponse; +import subway.dto.response.StationResponse; +import subway.ui.EndpointType; + +import java.util.List; +import java.util.stream.Collectors; + +@Transactional +@Service +public class LineStationService { + + private final LineService lineService; + private final StationService stationService; + private final PathService pathService; + + public LineStationService(final LineService lineService, final StationService stationService, final PathService pathService) { + this.lineService = lineService; + this.stationService = stationService; + this.pathService = pathService; + } + + public void addInitStations(final Long id, final Long upStationId, final Long downStationId, final int distance) { + final Line line = lineService.findById(id); + final Station upStation = stationService.findById(upStationId); + final Station downStation = stationService.findById(downStationId); + + line.addInitStations(upStation, downStation, distance); + + lineService.save(line); + updatePath(); + } + + private void updatePath() { + final Lines lines = lineService.findAll(); + pathService.update(lines); + } + + public void addEndpoint(final EndpointType endpointType, final Long id, final Long stationId, final int distance) { + final Line line = lineService.findById(id); + final Station station = stationService.findById(stationId); + + if (EndpointType.UP == endpointType) { + line.addUpEndpoint(station, distance); + } else if (EndpointType.DOWN == endpointType) { + line.addDownEndpoint(station, distance); + } + + lineService.save(line); + updatePath(); + } + + public void addIntermediate(final Long id, final Long stationId, final Long prevStationId, final int distance) { + final Line line = lineService.findById(id); + final Station station = stationService.findById(stationId); + final Station prevStation = stationService.findById(prevStationId); + + line.addIntermediate(station, prevStation, distance); + + lineService.save(line); + updatePath(); + } + + public void deleteStationInLine(final Long id, final Long stationId) { + final Line line = lineService.findById(id); + final Station station = stationService.findById(stationId); + + line.deleteSections(station); + + lineService.save(line); + updatePath(); + } + + public void deleteStation(final Long stationId) { + final Lines lines = lineService.findAll(); + final Station station = stationService.findById(stationId); + + lines.deleteStation(station); + + stationService.deleteStationById(stationId); + updatePath(); + } + + @Transactional(readOnly = true) + public List findAll() { + return lineService.findAll().getLines().stream() + .map(this::mapToResponse) + .collect(Collectors.toList()); + } + + @Transactional(readOnly = true) + public LineStationResponse findByLineId(final Long id) { + final Line line = lineService.findById(id); + return mapToResponse(line); + } + + private LineStationResponse mapToResponse(final Line line) { + final List stationResponses = mapToStationResponses(line.getAllStations()); + final List getDistances = line.getAllDistances(); + return new LineStationResponse(stationResponses, getDistances); + } + + private List mapToStationResponses(final List stations) { + return stations.stream() + .map(StationResponse::of) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/subway/application/PathService.java b/src/main/java/subway/application/PathService.java new file mode 100644 index 000000000..b6616f3f3 --- /dev/null +++ b/src/main/java/subway/application/PathService.java @@ -0,0 +1,47 @@ +package subway.application; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import subway.domain.Lines; +import subway.domain.Station; +import subway.domain.fare.FareCalculator; +import subway.domain.graph.SubwayGraph; +import subway.dto.response.PathResponse; + +import java.util.List; +import java.util.stream.Collectors; + +@Transactional +@Service +public class PathService { + private final StationService stationService; + private final FareCalculator fareCalculator; + private final SubwayGraph subwayGraph; + + public PathService(final StationService stationService, final FareCalculator fareCalculator, final SubwayGraph subwayGraph) { + this.stationService = stationService; + this.fareCalculator = fareCalculator; + this.subwayGraph = subwayGraph; + } + + @Transactional(readOnly = true) + public PathResponse findShortestPath(final Long source, final Long target) { + final Station sourceStation = stationService.findById(source); + final Station targetStation = stationService.findById(target); + + final List shortestPaths = subwayGraph.findPath(sourceStation, targetStation); + final int distanceSum = subwayGraph.calculateDistanceSum(sourceStation, targetStation); + + final int fareByDistance = fareCalculator.calculate(distanceSum); + + return new PathResponse(mapToString(shortestPaths), distanceSum, fareByDistance); + } + + private List mapToString(final List shortestPaths) { + return shortestPaths.stream().map(Station::getName).collect(Collectors.toList()); + } + + public void update(final Lines lines) { + subwayGraph.update(lines); + } +} diff --git a/src/main/java/subway/application/StationService.java b/src/main/java/subway/application/StationService.java index 1da7e07f9..5ea1ebf4f 100644 --- a/src/main/java/subway/application/StationService.java +++ b/src/main/java/subway/application/StationService.java @@ -2,136 +2,50 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import subway.dao.StationDao; -import subway.dao.SubwayMapRepository; -import subway.domain.Line; -import subway.domain.Section; import subway.domain.Station; -import subway.domain.SubwayMap; -import subway.dto.CreateType; -import subway.dto.StationRequest; -import subway.dto.StationResponse; +import subway.dto.request.StationRequest; +import subway.dto.response.StationResponse; +import subway.persistance.StationDao; import java.util.List; import java.util.stream.Collectors; -@Service @Transactional +@Service public class StationService { private final StationDao stationDao; - private final LineService lineService; - private final SubwayMapRepository subwayMapRepository; - public StationService(final StationDao stationDao, final LineService lineService, final SubwayMapRepository subwayMapRepository) { + public StationService(final StationDao stationDao) { this.stationDao = stationDao; - this.lineService = lineService; - this.subwayMapRepository = subwayMapRepository; } - public List findStationsByLineId(final Long lineId) { - final SubwayMap subwayMap = subwayMapRepository.find(); - - final Line line = lineService.findLineById(lineId); - - final List stations = subwayMap.stationsInLine(line); - return stations.stream() - .map(StationResponse::of) - .collect(Collectors.toList()); + public StationResponse saveStation(final StationRequest stationRequest) { + final Station station = stationDao.insert(new Station(stationRequest.getName())); + return StationResponse.of(station); } - public void saveStation(final List stationRequests) { - final CreateType type = CreateType.valueOf(stationRequests.get(0).getCreateType()); - final Line line = lineService.findLineByName(stationRequests.get(0).getLine()); - - if (type == CreateType.INIT) { - final StationRequest requestUp = stationRequests.get(0); - final StationRequest requestDown = stationRequests.get(1); - - final SubwayMap subwayMap = new SubwayMap(); - final Station upStation = new Station(requestUp.getName()); - final Station downStation = new Station(requestDown.getName()); - final Section upToDown = new Section(requestUp.getNextDistance(), upStation, downStation, line); - final Section downToUp = new Section(requestUp.getNextDistance(), downStation, upStation, line); - - subwayMap.addInitialStations(upToDown, downToUp); - - subwayMapRepository.save(subwayMap); - return; - } - final StationRequest request = stationRequests.get(0); - if (type == CreateType.UP) { - final SubwayMap subwayMap = subwayMapRepository.find(); - - final Station thisStation = new Station(request.getName()); - final Station nextStation = new Station(request.getNextStation()); - final Section thisToNext = new Section(request.getNextDistance(), thisStation, nextStation, line); - - subwayMap.addUpEndPoint(thisToNext); - - subwayMapRepository.save(subwayMap); - return; - } - if (type == CreateType.DOWN) { - final SubwayMap subwayMap = subwayMapRepository.find(); - - final Station thisStation = new Station(request.getName()); - final Station prevStation = new Station(request.getPreviousStation()); - final Section thisToPrev = new Section(request.getPreviousDistance(), thisStation, prevStation, line); - - subwayMap.addDownEndPoint(thisToPrev); - - subwayMapRepository.save(subwayMap); - return; - } - if (type == CreateType.MID) { - validateRequestMid(request); - - final SubwayMap subwayMap = subwayMapRepository.find(); - - final Station prevStation = new Station(request.getPreviousStation()); - final Station thisStation = new Station(request.getName()); - final Station nextStation = new Station(request.getNextStation()); - final Section thisToPrev = new Section(request.getPreviousDistance(), thisStation, prevStation, line); - final Section thisToNext = new Section(request.getNextDistance(), thisStation, nextStation, line); - - subwayMap.addIntermediateStation(thisToPrev, thisToNext); - - subwayMapRepository.save(subwayMap); - return; - } - throw new IllegalArgumentException("잘못된 타입입니다."); + public void updateStation(final Long id, final StationRequest stationRequest) { + stationDao.update(new Station(id, stationRequest.getName())); } - private void validateRequestMid(final StationRequest request) { - if (request.getNextStation() == null || request.getNextDistance() == null) { - throw new IllegalArgumentException("잘못된 중간역을 등록했습니다: 다음 역 오류"); - } - if (request.getPreviousStation() == null || request.getPreviousDistance() == null) { - throw new IllegalArgumentException("잘못된 중간역을 등록했습니다: 이전 역 오류"); - } + public void deleteStationById(final Long id) { + stationDao.deleteById(id); } public StationResponse findStationResponseById(final Long id) { - return StationResponse.of(stationDao.findById(id)); + return StationResponse.of(findById(id)); } + @Transactional(readOnly = true) public List findAllStationResponses() { - final List stations = stationDao.findAll(); - - return stations.stream() + return stationDao.findAll().stream() .map(StationResponse::of) .collect(Collectors.toList()); } - public void updateStation(final Long id, final StationRequest stationRequest) { - stationDao.update(new Station(id, stationRequest.getName())); - } - - public void deleteStationById(final Long id) { - final SubwayMap subwayMap = subwayMapRepository.find(); - - subwayMap.deleteStation(id); - - subwayMapRepository.save(subwayMap); + @Transactional(readOnly = true) + public Station findById(final Long id) { + return stationDao.findById(id) + .orElseThrow(() -> new IllegalArgumentException("id에 해당하는 역이 존재하지 않습니다.")); } } diff --git a/src/main/java/subway/dao/LineDao.java b/src/main/java/subway/dao/LineDao.java deleted file mode 100644 index 5360a3625..000000000 --- a/src/main/java/subway/dao/LineDao.java +++ /dev/null @@ -1,81 +0,0 @@ -package subway.dao; - -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; -import org.springframework.jdbc.core.simple.SimpleJdbcInsert; -import org.springframework.stereotype.Repository; -import subway.dao.entity.LineEntity; -import subway.domain.Line; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@Repository -public class LineDao { - private final JdbcTemplate jdbcTemplate; - private final SimpleJdbcInsert insertAction; - - private final RowMapper rowMapper = (rs, rowNum) -> - new Line( - rs.getLong("id"), - rs.getString("name"), - rs.getString("color") - ); - - private final RowMapper entityRowMapper = (rs, rowNum) -> - new LineEntity( - rs.getLong("id"), - rs.getString("name"), - rs.getString("color"), - rs.getLong("up_endpoint_id") - ); - - public LineDao(final JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - this.insertAction = new SimpleJdbcInsert(jdbcTemplate) - .withTableName("line") - .usingGeneratedKeyColumns("id"); - } - - public Line insert(final LineEntity line) { - final Map params = new HashMap<>(); - params.put("id", line.getId()); - params.put("name", line.getName()); - params.put("color", line.getColor()); - params.put("up_endpoint_id", line.getUpEndpointId()); - - final Long lineId = insertAction.executeAndReturnKey(params).longValue(); - return new Line(lineId, line.getName(), line.getColor()); - } - - public List findAll() { - final String sql = "select id, name, color, up_endpoint_id from LINE"; - return jdbcTemplate.query(sql, entityRowMapper); - } - - public Line findById(final Long id) { - final String sql = "select (id, name, color) from LINE WHERE id = ?"; - return jdbcTemplate.queryForObject(sql, rowMapper, id); - } - - public Line findByName(final String name) { - final String sql = "select * from LINE WHERE name = ?"; - return jdbcTemplate.queryForObject(sql, rowMapper, name); - } - - public void update(final Long id, final Line newLine) { - final String sql = "update LINE set name = ?, color = ? where id = ?"; - jdbcTemplate.update(sql, newLine.getName(), newLine.getColor(), id); - } - - public void deleteById(final Long id) { - jdbcTemplate.update("delete from Line where id = ?", id); - } - - public void deleteAll() { - final String sql = "DELETE FROM line"; - - jdbcTemplate.update(sql); - } -} diff --git a/src/main/java/subway/dao/SectionDao.java b/src/main/java/subway/dao/SectionDao.java deleted file mode 100644 index ca1b52fe9..000000000 --- a/src/main/java/subway/dao/SectionDao.java +++ /dev/null @@ -1,63 +0,0 @@ -package subway.dao; - -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Repository; -import subway.dao.entity.SectionEntity; -import subway.domain.Section; - -import java.util.List; - -@Repository -public class SectionDao { - - private final JdbcTemplate jdbcTemplate; - private final RowMapper sectionEntityRowMapper = (rs, rowNum) -> { - Long id = rs.getLong("id"); - int distance = rs.getInt("distance"); - Long departure = rs.getLong("departure_id"); - Long arrival = rs.getLong("arrival_id"); - Long lineId = rs.getLong("line_id"); - return new SectionEntity(id, distance, departure, arrival, lineId); - }; - - public SectionDao(final JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - } - - public void insertSection(final Section section) { - final String sql = "INSERT INTO section(departure_id, arrival_id, distance, line_id) VALUES (?, ?, ?, ?)"; - - jdbcTemplate.update(sql, section.getDeparture().getId(), section.getArrival().getId(), section.getDistance(), section.getLine().getId()); - } - - public List findByLineId(final Long lineId) { - final String sql = "SELECT * FROM section WHERE line_id = ?"; - - return jdbcTemplate.query(sql, sectionEntityRowMapper, lineId); - } - - public SectionEntity findByStationIds(final Long departureId, final Long arrivalId) { - final String sql = "SELECT * FROM section WHERE departure_id = ? AND arrival_id = ?"; - - return jdbcTemplate.queryForObject(sql, sectionEntityRowMapper, departureId, arrivalId); - } - - public void update(final Section before, final Section after) { - final String sql = "UPDATE section SET distance = ?, departure_id = ?, arrival_id = ? WHERE departure_id = ? AND arrival_id = ?"; - - jdbcTemplate.update(sql, after.getDistance(), after.getDeparture().getId(), after.getArrival().getId(), before.getDeparture().getId(), before.getArrival().getId()); - } - - public List findAll() { - final String sql = "SELECT * FROM section"; - - return jdbcTemplate.query(sql, sectionEntityRowMapper); - } - - public void deleteAll() { - String sql = "DELETE FROM section"; - - jdbcTemplate.update(sql); - } -} diff --git a/src/main/java/subway/dao/SubwayMapRepository.java b/src/main/java/subway/dao/SubwayMapRepository.java deleted file mode 100644 index 682e4ab64..000000000 --- a/src/main/java/subway/dao/SubwayMapRepository.java +++ /dev/null @@ -1,94 +0,0 @@ -package subway.dao; - -import org.springframework.stereotype.Repository; -import subway.dao.entity.LineEntity; -import subway.dao.entity.SectionEntity; -import subway.domain.*; - -import java.util.*; -import java.util.function.Function; -import java.util.stream.Collectors; - -@Repository -public class SubwayMapRepository { - - private final StationDao stationDao; - private final SectionDao sectionDao; - private final LineDao lineDao; - - public SubwayMapRepository(final StationDao stationDao, final SectionDao sectionDao, final LineDao lineDao) { - this.stationDao = stationDao; - this.sectionDao = sectionDao; - this.lineDao = lineDao; - } - - public SubwayMap find() { - final List stations = stationDao.findAll(); - final List sectionEntities = sectionDao.findAll(); - final List lineEntities = lineDao.findAll(); - final Map lineMap = lineEntities.stream() - .collect(Collectors.toMap(LineEntity::toLine, - entity -> stations.stream() - .filter(station -> station.getId().equals(entity.getUpEndpointId())) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 종점역입니다.")) - )); - return generateMap(stations, sectionEntities, new HashMap<>(lineMap)); - } - - private SubwayMap generateMap(final List stations, final List sectionEntities, final Map lineMap) { - return new SubwayMap(stations.stream() - .collect(Collectors.toMap( - Function.identity(), - station -> new Sections(mapToSection(sectionEntities, station)) - )), lineMap); - } - - private List
mapToSection(final List sectionEntities, final Station station) { - System.out.println(sectionEntities.get(0).getLineId()); - return sectionEntities.stream() - .filter(entity -> entity.getDepartureId() == station.getId()) - .map(entity -> new Section(entity.getDistance(), - station, stationDao.findById(entity.getArrivalId()), - lineDao.findById(entity.getLineId())) - ).collect(Collectors.toList()); - } - - public void save(final SubwayMap subwayMap) { - // 있으면 update 없으면 save - stationDao.deleteAll(); - sectionDao.deleteAll(); - lineDao.deleteAll(); - - final Map subwayMap1 = subwayMap.getSubwayMap(); - - List stations = new ArrayList<>(); - for (Station station : subwayMap1.keySet()) { - Station insert = stationDao.insert(station); - station.setId(insert); - stations.add(insert); - } - - final List
sections = mapToSections(subwayMap1); - sections.forEach(sectionDao::insertSection); - - final Set lines = new HashSet<>(mapToLines(sections)); - final List lineEntities = lines.stream() - .map(line -> new LineEntity(line.getId(), line.getName(), line.getColor(), subwayMap.getEndpointMap().get(line).getId())) - .collect(Collectors.toList()); - lineEntities.forEach(lineDao::insert); - } - - private List
mapToSections(final Map subwayMap1) { - return subwayMap1.values().stream() - .map(Sections::getSections) - .flatMap(List::stream) - .collect(Collectors.toList()); - } - - private List mapToLines(final List
sections) { - return sections.stream() - .map(Section::getLine) - .collect(Collectors.toList()); - } -} diff --git a/src/main/java/subway/dao/entity/LineEntity.java b/src/main/java/subway/dao/entity/LineEntity.java deleted file mode 100644 index bdecac37d..000000000 --- a/src/main/java/subway/dao/entity/LineEntity.java +++ /dev/null @@ -1,41 +0,0 @@ -package subway.dao.entity; - -import subway.domain.Line; - -public class LineEntity { - private final Long id; - private final String name; - private final String color; - private final Long upEndpointId; - - public LineEntity(final String name, final String color) { - this(null, name, color, null); - } - - public LineEntity(final Long id, final String name, final String color, final Long upEndpointId) { - this.id = id; - this.name = name; - this.color = color; - this.upEndpointId = upEndpointId; - } - - public Long getId() { - return id; - } - - public String getName() { - return name; - } - - public String getColor() { - return color; - } - - public Long getUpEndpointId() { - return upEndpointId; - } - - public Line toLine() { - return new Line(id, name, color); - } -} diff --git a/src/main/java/subway/dao/entity/SectionEntity.java b/src/main/java/subway/dao/entity/SectionEntity.java deleted file mode 100644 index 280df54c9..000000000 --- a/src/main/java/subway/dao/entity/SectionEntity.java +++ /dev/null @@ -1,37 +0,0 @@ -package subway.dao.entity; - -public class SectionEntity { - private final Long id; - private final int distance; - private final Long departureId; - private final Long arrivalId; - private final Long lineId; - - public SectionEntity(final Long id, final int distance, final Long departureId, final Long arrivalId, final Long lineId) { - this.id = id; - this.distance = distance; - this.departureId = departureId; - this.arrivalId = arrivalId; - this.lineId = lineId; - } - - public Long getId() { - return id; - } - - public int getDistance() { - return distance; - } - - public Long getDepartureId() { - return departureId; - } - - public Long getArrivalId() { - return arrivalId; - } - - public Long getLineId() { - return lineId; - } -} diff --git a/src/main/java/subway/domain/Direction.java b/src/main/java/subway/domain/Direction.java deleted file mode 100644 index 0e8eca2e3..000000000 --- a/src/main/java/subway/domain/Direction.java +++ /dev/null @@ -1,32 +0,0 @@ -package subway.domain; - -import java.util.Arrays; - -public enum Direction { - UP("up"), - DOWN("down"); - - private final String value; - - Direction(final String value) { - this.value = value; - } - - public static Direction getDirection(final String name) { - return Arrays.stream(Direction.values()) - .filter(direction -> direction.value.equals(name)) - .findAny() - .orElseThrow(() -> new IllegalArgumentException("방향의 이름이 올바르지 않습니다.")); - } - - public Direction getOpposite() { - if (this.equals(UP)) { - return DOWN; - } - return UP; - } - - public String getValue() { - return value; - } -} diff --git a/src/main/java/subway/domain/Distance.java b/src/main/java/subway/domain/Distance.java new file mode 100644 index 000000000..d6eb8b7e8 --- /dev/null +++ b/src/main/java/subway/domain/Distance.java @@ -0,0 +1,50 @@ +package subway.domain; + +import subway.exceptions.IllegalDistanceException; + +import java.util.Objects; + +public class Distance { + private static final int MIN_VALUE = 1; + + private final int value; + + public Distance(final int value) { + validate(value); + this.value = value; + } + + private void validate(final int value) { + if (value < MIN_VALUE) { + throw new IllegalDistanceException("거리는 1 이상만 가능합니다."); + } + } + + public Distance sub(final Distance distance) { + if (value - distance.value < MIN_VALUE) { + throw new IllegalDistanceException("거리 계산 결과는 음수 또는 0이 될 수 없습니다"); + } + return new Distance(value - distance.value); + } + + public Distance sum(final Distance distance) { + return new Distance(value + distance.value); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final Distance distance = (Distance) o; + return value == distance.value; + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + public int getValue() { + return value; + } +} diff --git a/src/main/java/subway/domain/Line.java b/src/main/java/subway/domain/Line.java index 1be70f0ae..cdece161d 100644 --- a/src/main/java/subway/domain/Line.java +++ b/src/main/java/subway/domain/Line.java @@ -1,26 +1,62 @@ package subway.domain; -import java.util.Objects; +import java.util.List; +import java.util.stream.Collectors; public class Line { - private Long id; + private final Long id; + private final Sections sections; private String name; private String color; - public Line() { - } - - public Line(final String name, final String color) { + public Line(final Long id, final String name, final String color, final Sections sections) { + this.id = id; this.name = name; this.color = color; + this.sections = sections; } public Line(final Long id, final String name, final String color) { - this.id = id; + this(id, name, color, new Sections()); + } + + + public void addInitStations(final Station up, final Station down, final int distance) { + sections.addInitStations(up, down, new Distance(distance)); + } + + + public void addUpEndpoint(final Station station, final int distance) { + sections.addUpEndpoint(station, new Distance(distance)); + } + + public void addDownEndpoint(final Station station, final int distance) { + sections.addDownEndpoint(station, new Distance(distance)); + } + + public void addIntermediate(final Station station, final Station prevStation, final int distance) { + sections.addIntermediate(station, prevStation, new Distance(distance)); + } + + public void deleteSections(final Station station) { + sections.delete(station); + } + + public void update(final String name, final String color) { this.name = name; this.color = color; } + public List getAllStations() { + return sections.getAllStations(); + } + + public List getAllDistances() { + return sections.getAllDistances().stream() + .map(Distance::getValue) + .collect(Collectors.toList()); + } + public Long getId() { return id; } @@ -33,19 +69,7 @@ public String getColor() { return color; } - @Override - public int hashCode() { - return Objects.hash(id, name, color); - } - - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - final Line line = (Line) o; - if (id == null || line.id == null) { - return Objects.equals(name, line.name) && Objects.equals(color, line.color); - } - return Objects.equals(id, line.id) && Objects.equals(name, line.name) && Objects.equals(color, line.color); + public List
getSections() { + return sections.getSections(); } } diff --git a/src/main/java/subway/domain/LineRepository.java b/src/main/java/subway/domain/LineRepository.java new file mode 100644 index 000000000..56390c78e --- /dev/null +++ b/src/main/java/subway/domain/LineRepository.java @@ -0,0 +1,15 @@ +package subway.domain; + +import java.util.Optional; + +public interface LineRepository { + + Optional findById(Long id); + + Lines findAll(); + + Line save(Line line); + + void delete(Line line); + +} diff --git a/src/main/java/subway/domain/Lines.java b/src/main/java/subway/domain/Lines.java new file mode 100644 index 000000000..d1b19c7b4 --- /dev/null +++ b/src/main/java/subway/domain/Lines.java @@ -0,0 +1,33 @@ +package subway.domain; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class Lines { + private final List lines; + + public Lines(final List lines) { + this.lines = new ArrayList<>(lines); + } + + public void deleteStation(final Station station) { + lines.forEach(line -> line.deleteSections(station)); + } + + public List
getAllSections() { + return lines.stream() + .flatMap(line -> line.getSections().stream()) + .collect(Collectors.toList()); + } + + public List getAllStations() { + return lines.stream() + .flatMap(line -> line.getAllStations().stream()) + .collect(Collectors.toList()); + } + + public List getLines() { + return List.copyOf(lines); + } +} diff --git a/src/main/java/subway/domain/Section.java b/src/main/java/subway/domain/Section.java index c4df70f6a..3e41354af 100644 --- a/src/main/java/subway/domain/Section.java +++ b/src/main/java/subway/domain/Section.java @@ -1,62 +1,85 @@ package subway.domain; +import subway.exceptions.IllegalStationException; +import subway.exceptions.SectionStateException; + import java.util.Objects; public class Section { - private final int distance; - private final Station departure; - private final Station arrival; - private final Line line; + private final Station up; + private final Station down; + private final Distance distance; - public Section(int distance, Station departure, Station arrival, Line line) { + public Section(final Station up, final Station down, final Distance distance) { + validateSameStation(up, down); + this.up = up; + this.down = down; this.distance = distance; - this.departure = departure; - this.arrival = arrival; - this.line = line; } - public Section getReverse() { - return new Section(distance, arrival, departure, line); + private void validateSameStation(final Station up, final Station down) { + if (up.equals(down)) { + throw new IllegalStationException("구간의 두 역이 같을 수 없습니다."); + } } - public boolean LineEquals(Section other) { - return line.equals(other.getLine()); + public Section connectToUp(final Station station, final Distance distance) { + return new Section(station, up, distance); } - public boolean isDeparture(Station station) { - return departure.equals(station); + public Section connectToDown(final Station station, final Distance distance) { + return new Section(down, station, distance); } - public boolean isArrival(Station station) { - return arrival.getName().equals(station.getName()); + public Section connectIntermediate(final Station station, final Distance distance) { + return new Section(up, station, distance); } - public int getDistance() { - return distance; + public Distance subDistance(final Distance distance) { + return this.distance.sub(distance); + } + + public Section deleteStation(final Section upIsStation) { + if (!down.equals(upIsStation.up)) { + throw new SectionStateException("삭제하려는 두 구간이 이어져있지 않습니다."); + } + return new Section(up, upIsStation.down, distance.sum(upIsStation.distance)); } - public Station getDeparture() { - return departure; + public boolean contains(final Station station) { + return up.equals(station) || down.equals(station); } - public Station getArrival() { - return arrival; + public boolean isUp(final Station station) { + return up.equals(station); } - public Line getLine() { - return line; + public boolean isDown(final Station station) { + return down.equals(station); } @Override - public boolean equals(Object o) { + public boolean equals(final Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - Section section = (Section) o; - return distance == section.distance && Objects.equals(departure, section.departure) && Objects.equals(arrival, section.arrival) && Objects.equals(line, section.line); + final Section section = (Section) o; + return Objects.equals(distance, section.distance) && Objects.equals(up, section.up) && Objects.equals(down, section.down); } @Override public int hashCode() { - return Objects.hash(distance, departure, arrival, line); + return Objects.hash(up, down, distance); + } + + public Station getUp() { + return up; + } + + public Station getDown() { + return down; + } + + public Distance getDistance() { + return distance; } } diff --git a/src/main/java/subway/domain/Sections.java b/src/main/java/subway/domain/Sections.java index ae31f0702..121c9e582 100644 --- a/src/main/java/subway/domain/Sections.java +++ b/src/main/java/subway/domain/Sections.java @@ -1,102 +1,164 @@ package subway.domain; +import subway.exceptions.SectionStateException; + import java.util.ArrayList; import java.util.List; -import java.util.Set; +import java.util.function.Predicate; import java.util.stream.Collectors; +import java.util.stream.IntStream; public class Sections { private final List
sections; + public Sections() { + this.sections = new ArrayList<>(); + } + public Sections(final List
sections) { this.sections = new ArrayList<>(sections); } - public Sections updateArrival(final Station arrival, final Section section) { - final Section found = getSameLineAndArrivalIs(arrival, section); + public void addInitStations(final Station up, final Station down, final Distance distance) { + validateInit(); + final Section section = new Section(up, down, distance); + sections.add(section); + } + + private void validateInit() { + if (sections.size() > 0) { + throw new SectionStateException("라인에 이미 등록된 역이 있습니다."); + } + } + + public void addUpEndpoint(final Station station, final Distance distance) { + validateHasSize(); - final ArrayList
update = new ArrayList<>(sections); - update.remove(found); - update.add(section); - return new Sections(update); + final Section section = sections.get(0); + final Section connected = section.connectToUp(station, distance); + sections.add(0, connected); } - private Section getSameLineAndArrivalIs(final Station arrival, final Section other) { - return sections.stream() - .filter(section -> section.LineEquals(other)) - .filter(section -> section.isArrival(arrival)) - .findAny() - .orElseThrow(() -> new IllegalArgumentException("같은라인에 arrival에 해당하는 section이 없습니다")); + public void addDownEndpoint(final Station station, final Distance distance) { + validateHasSize(); + + final Section section = sections.get(sections.size() - 1); + final Section connected = section.connectToDown(station, distance); + sections.add(connected); + } + + public void addIntermediate(final Station station, final Station prevStation, final Distance distance) { + validateHasSize(); + + final Section prevToNext = getSectionUpIs(prevStation); + final Section prevToThis = prevToNext.connectIntermediate(station, distance); + final Section thisToNext = new Section(station, prevToNext.getDown(), prevToNext.subDistance(distance)); // 42 + + final int index = getIndex(prevToNext); + + sections.remove(index); + sections.add(index, thisToNext); + sections.add(index, prevToThis); } - public void validateDistanceBetween(final Station arrival, final Section s1, final Section s2) { - final Section section = getSectionTo(arrival); - if (section.getDistance() != s1.getDistance() + s2.getDistance()) { - throw new IllegalArgumentException("요청의 이전역과 다음역 사이의 거리가 잘못되었습니다."); + private void validateHasSize() { + if (sections.size() < 1) { + throw new SectionStateException("라인에 등록되어 있는 역이 없습니다."); } } - private Section getSectionTo(final Station arrival) { - return sections.stream() - .filter(section -> section.isArrival(arrival)) - .findAny() - .orElseThrow(() -> new IllegalArgumentException("두 역이 연결되어 있지 않습니다.")); + public void delete(final Station station) { + if (isInit()) { + sections.clear(); + return; + } + if (isMid(station)) { + deleteMidStation(station); + return; + } + if (isEndpoint(station)) { + final Section found = getSectionContains(station); + sections.remove(found); + } } - public int countSectionByLine(final Line line) { - return (int) sections.stream() - .filter(section -> section.getLine().equals(line)) - .count(); + private boolean isInit() { + return sections.size() < 2; } - public List
getSameLineSections(final Line line) { + private boolean isMid(final Station station) { return sections.stream() - .filter(section -> section.getLine().equals(line)) - .collect(Collectors.toList()); + .skip(1) + .anyMatch(section -> section.isUp(station)); } - public List
getSections() { - return sections; + private boolean isEndpoint(final Station station) { + return sections.get(0).isUp(station) + || sections.get(sections.size() - 1).isDown(station); } - public boolean hasSameLine(final Section other) { - return sections.stream() - .anyMatch(section -> section.LineEquals(other)); + private void deleteMidStation(final Station station) { + final Section upIsStation = getSectionUpIs(station); + final Section downIsStation = getSectionDownIs(station); + final Section updatedSection = downIsStation.deleteStation(upIsStation); + + update(downIsStation, updatedSection); } - public Section getSameLineSection(final Line line) { + private void update(final Section downIsStation, final Section updatedSection) { + final int index = getIndex(downIsStation); + sections.remove(index + 1); + sections.remove(index); + sections.add(index, updatedSection); + } + + private Section getSectionContains(final Station station) { return sections.stream() - .filter(section -> section.getLine().equals(line)) + .filter(section -> section.contains(station)) .findAny() - .orElseThrow(); + .orElseThrow(() -> new SectionStateException("역을 포함하고 있는 구간이 존재하지 않습니다.")); } - public Sections addSection(final Section section) { - final ArrayList
added = new ArrayList<>(sections); - added.add(section); - return new Sections(added); + private Section getSectionUpIs(final Station station) { + return getSectionFrom(section -> section.isUp(station)); } - public int countSameLine(final Section other) { - return (int) sections.stream() - .filter(section -> section.LineEquals(other)) - .count(); + private Section getSectionDownIs(final Station station) { + return getSectionFrom(section -> section.isDown(station)); } - public Set getLinesIncludingStation() { + private Section getSectionFrom(final Predicate
direction) { return sections.stream() - .map(Section::getLine) - .collect(Collectors.toSet()); + .filter(direction) + .findAny() + .orElseThrow(() -> new SectionStateException("라인에 등록되지 않은 역입니다.")); } - public Sections removeArrival(final Station station) { - final List
removed = new ArrayList<>(sections); - final Section section = removed.stream() - .filter(s -> s.isArrival(station)) + private int getIndex(final Section section) { + return IntStream.range(0, sections.size()) + .filter(i -> sections.get(i).equals(section)) .findAny() - .orElseThrow(); - removed.remove(section); - return new Sections(removed); + .orElseThrow(() -> new SectionStateException("라인에 등록되지 않은 구간입니다.")); } -} + public List getAllStations() { + final List stations = sections.stream() + .map(Section::getDown) + .collect(Collectors.toList()); + if (sections.size() != 0) { + stations.add(0, sections.get(0).getUp()); + } + + return stations; + } + + public List getAllDistances() { + return sections.stream() + .map(Section::getDistance) + .collect(Collectors.toList()); + } + + public List
getSections() { + return List.copyOf(sections); + } +} \ No newline at end of file diff --git a/src/main/java/subway/domain/Station.java b/src/main/java/subway/domain/Station.java index 15d4a2205..6ea9f2208 100644 --- a/src/main/java/subway/domain/Station.java +++ b/src/main/java/subway/domain/Station.java @@ -3,11 +3,8 @@ import java.util.Objects; public class Station { - private Long id; - private String name; - - public Station() { - } + private final Long id; + private final String name; public Station(final Long id, final String name) { this.id = id; @@ -15,33 +12,30 @@ public Station(final Long id, final String name) { } public Station(final String name) { - this.name = name; - } - - public Long getId() { - return id; - } - - public void setId(Station other) { - this.id = other.id; - this.name = other.name; + this(null, name); } - public String getName() { - return name; + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (getClass() != o.getClass()) return false; + final Station station = (Station) o; + if (id == null || station.id == null) { + return Objects.equals(name, station.name); + } + return Objects.equals(id, station.id) && Objects.equals(name, station.name); } @Override public int hashCode() { - return Objects.hash(name); + return Objects.hash(id, name); } - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - final Station station = (Station) o; + public Long getId() { + return id; + } - return name.equals(station.name); + public String getName() { + return name; } } diff --git a/src/main/java/subway/domain/SubwayMap.java b/src/main/java/subway/domain/SubwayMap.java deleted file mode 100644 index 860c9837a..000000000 --- a/src/main/java/subway/domain/SubwayMap.java +++ /dev/null @@ -1,228 +0,0 @@ -package subway.domain; - -import java.util.*; -import java.util.function.Function; -import java.util.stream.Collectors; - -public class SubwayMap { - - private final Map subwayMap; - private final Map endpointMap; - - public SubwayMap() { - this.subwayMap = new HashMap<>(); - this.endpointMap = new HashMap<>(); - } - - public SubwayMap(final Map subwayMap, final Map endpointMap) { - this.subwayMap = new HashMap<>(subwayMap); - this.endpointMap = new HashMap<>(endpointMap); - } - - public Map> getAllStationsGroupByLine() { - return endpointMap.keySet().stream() - .collect(Collectors.toMap(Function.identity(), - this::stationsInLine)); - } - - public List stationsInLine(final Line line) { - final Station upEndpoint = endpointMap.get(line); - final List stations = new ArrayList<>(); - - Optional
currentSection = getNextSection(line, upEndpoint, null); - stations.add(currentSection.orElseThrow().getDeparture()); - while (currentSection.isPresent()) { - final Station visited = currentSection.orElseThrow().getDeparture(); - final Station current = currentSection.orElseThrow().getArrival(); - stations.add(currentSection.orElseThrow().getArrival()); - currentSection = getNextSection(line, current, visited); - } - return stations; - } - - private Optional
getNextSection(final Line line, final Station current, final Station visited) { - final List
sameLineSections = subwayMap.get(current).getSameLineSections(line); - final int length = sameLineSections.size(); - if (length == 1) { - if (sameLineSections.get(0).getArrival().equals(visited)) { - return Optional.empty(); - } - return Optional.of(sameLineSections.get(0)); - } - if (length == 2) { - return sameLineSections.stream(). - filter(s -> !s.isArrival(visited)) - .findAny(); - } - throw new IllegalArgumentException("한 라인에 갈림길이 존재할 수 없습니다."); - } - - public void addIntermediateStation(final Section thisToPrev, final Section thisToNext) { - final Station prevStation = thisToPrev.getArrival(); - final Station thisStation = thisToPrev.getDeparture(); - final Station nextStation = thisToNext.getArrival(); - - final Sections prevSections = subwayMap.get(prevStation); - prevSections.validateDistanceBetween(nextStation, thisToPrev, thisToNext); - - subwayMap.put(thisStation, new Sections(List.of(thisToPrev, thisToNext))); - updateSections(prevStation, thisToPrev.getReverse(), nextStation, thisToNext.getReverse()); - } - - private void updateSections(final Station prevStation, final Section prevToThis, final Station nextStation, final Section nextToThis) { - final Sections prevSections = subwayMap.get(prevStation); - final Sections updatedPrevSections = prevSections.updateArrival(nextStation, prevToThis); - subwayMap.put(prevStation, updatedPrevSections); - - final Sections nextSections = subwayMap.get(nextStation); - final Sections updatedNextSections = nextSections.updateArrival(prevStation, nextToThis); - subwayMap.put(nextStation, updatedNextSections); - } - - public void addInitialStations(final Section upToDown, final Section downToUp) { - if (lineHasStation(upToDown)) { - throw new IllegalArgumentException("해당 라인은 초기 등록이 아닙니다."); - } - - final Station upEndpoint = upToDown.getDeparture(); - final Station downEndpoint = upToDown.getArrival(); - - subwayMap.put(upEndpoint, new Sections(List.of(upToDown))); - subwayMap.put(downEndpoint, new Sections(List.of(downToUp))); - - endpointMap.put(upToDown.getLine(), upEndpoint); - } - - private boolean lineHasStation(final Section other) { - return subwayMap.values().stream() - .anyMatch(sections -> sections.hasSameLine(other)); - } - - public void addUpEndPoint(final Section thisToEnd) { - addEndPointStation(thisToEnd); - endpointMap.replace(thisToEnd.getLine(), thisToEnd.getDeparture()); - } - - private void addEndPointStation(final Section thisToEnd) { - final Station thisStation = thisToEnd.getDeparture(); - final Station endPoint = thisToEnd.getArrival(); - - validateIsEndpoint(thisToEnd, endPoint); - - subwayMap.put(thisStation, new Sections(List.of(thisToEnd))); - - addSectionToEndpoint(endPoint, thisToEnd.getReverse()); - } - - private void validateIsEndpoint(final Section thisToEnd, final Station endPoint) { - final Sections sections = subwayMap.get(endPoint); - if (sections.countSameLine(thisToEnd) != 1) { - throw new IllegalArgumentException("도착지로 선택한 역이 종점이 아닙니다."); - } - } - - private void addSectionToEndpoint(final Station station, final Section section) { - final Sections sections = subwayMap.get(station); - if (sections == null) { - throw new IllegalArgumentException("역이 존재하지 않습니다."); - } - final Sections addedSections = sections.addSection(section); - subwayMap.put(station, addedSections); - } - - public void addDownEndPoint(final Section thisToEnd) { - addEndPointStation(thisToEnd); - } - - public void deleteStation(final Long id) { - final Station station = getStationById(id); - final Sections sectionsByStation = findSectionByStation(station); - - final Set includingLines = sectionsByStation.getLinesIncludingStation(); - - for (final Line line : includingLines) { - final Section sameLineSection = sectionsByStation.getSameLineSection(line); - - if (isLastTwoStations(line)) { - final Station arrival = sameLineSection.getArrival(); - subwayMap.remove(arrival); - continue; - } - if (isUpEndpoint(station, line)) { - final Station arrival = sameLineSection.getArrival(); - replaceEndSection(station, arrival); - endpointMap.replace(line, arrival); - continue; - } - if (isEndpoint(sectionsByStation, line)) { - final Station arrival = sameLineSection.getArrival(); - replaceEndSection(station, arrival); - continue; - } - - final List
sameLineSections = sectionsByStation.getSameLineSections(line); - final Section prevSection = sameLineSections.get(0); - final Section nextSection = sameLineSections.get(1); - final int distanceSum = prevSection.getDistance() + nextSection.getDistance(); - - final Station prevStation = prevSection.getArrival(); - final Station nextStation = nextSection.getArrival(); - - replaceSection(station, line, distanceSum, prevStation, nextStation); - replaceSection(station, line, distanceSum, nextStation, prevStation); - } - subwayMap.remove(station); - } - - private Station getStationById(final Long id) { - return subwayMap.keySet().stream() - .filter(s -> s.getId().equals(id)) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("삭제하려는 역이 존재하지 않습니다.")); - } - - public Sections findSectionByStation(final Station station) { - return subwayMap.get(station); - } - - private boolean isLastTwoStations(final Line line) { - final long countSectionsInLine = countSectionsInLine(line); - return countSectionsInLine == 2; - } - - private boolean isUpEndpoint(final Station station, final Line line) { - return station.getId().equals(endpointMap.get(line).getId()); - } - - private void replaceEndSection(final Station station, final Station arrival) { - final Sections arrivalSections = subwayMap.get(arrival); - final Sections removedSections = arrivalSections.removeArrival(station); - subwayMap.replace(arrival, removedSections); - } - - private boolean isEndpoint(final Sections sectionsByStation, final Line line) { - return sectionsByStation.countSectionByLine(line) == 1; - } - - private void replaceSection(final Station station, final Line line, final int distanceSum, final Station prevStation, final Station nextStation) { - final Sections prevSections = findSectionByStation(prevStation); - final Sections removedSections = prevSections.removeArrival(station); - final Sections addedSections = removedSections.addSection(new Section(distanceSum, prevStation, nextStation, line)); - subwayMap.replace(prevStation, addedSections); - } - - private long countSectionsInLine(final Line line) { - return subwayMap.values().stream() - .flatMap(sections -> sections.getSections().stream()) - .filter(section -> section.getLine().equals(line)) - .count(); - } - - public Map getSubwayMap() { - return new HashMap<>(subwayMap); - } - - public Map getEndpointMap() { - return new HashMap<>(endpointMap); - } -} diff --git a/src/main/java/subway/domain/fare/DiscountChain.java b/src/main/java/subway/domain/fare/DiscountChain.java new file mode 100644 index 000000000..8d0f1e20a --- /dev/null +++ b/src/main/java/subway/domain/fare/DiscountChain.java @@ -0,0 +1,19 @@ +package subway.domain.fare; + +import java.util.List; + +public class DiscountChain implements DistancePolicy { + + private final List distanceChains; + + public DiscountChain(final List distanceChains) { + this.distanceChains = distanceChains; + } + + @Override + public int calculate(final int distance) { + return distanceChains.stream() + .mapToInt(distancePolicy -> distancePolicy.calculate(distance)) + .sum(); + } +} diff --git a/src/main/java/subway/domain/fare/DistancePolicy.java b/src/main/java/subway/domain/fare/DistancePolicy.java new file mode 100644 index 000000000..83352c40a --- /dev/null +++ b/src/main/java/subway/domain/fare/DistancePolicy.java @@ -0,0 +1,5 @@ +package subway.domain.fare; + +public interface DistancePolicy { + int calculate(int distance); +} diff --git a/src/main/java/subway/domain/fare/FareCalculator.java b/src/main/java/subway/domain/fare/FareCalculator.java new file mode 100644 index 000000000..56eabcff0 --- /dev/null +++ b/src/main/java/subway/domain/fare/FareCalculator.java @@ -0,0 +1,16 @@ +package subway.domain.fare; + +import org.springframework.stereotype.Component; + +@Component +public class FareCalculator { + private final DistancePolicy distancePolicy; + + public FareCalculator(final DistancePolicy distancePolicy) { + this.distancePolicy = distancePolicy; + } + + public int calculate(final int distance) { + return distancePolicy.calculate(distance); + } +} diff --git a/src/main/java/subway/domain/fare/FareConfiguration.java b/src/main/java/subway/domain/fare/FareConfiguration.java new file mode 100644 index 000000000..7e20599e3 --- /dev/null +++ b/src/main/java/subway/domain/fare/FareConfiguration.java @@ -0,0 +1,21 @@ +package subway.domain.fare; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +@Configuration +public class FareConfiguration { + + private final List distancePolicies; + + public FareConfiguration(final List distancePolicies) { + this.distancePolicies = distancePolicies; + } + + @Bean + public DistancePolicy distancePolicy() { + return new DiscountChain(distancePolicies); + } +} diff --git a/src/main/java/subway/domain/fare/distance/BasicFarePolicy.java b/src/main/java/subway/domain/fare/distance/BasicFarePolicy.java new file mode 100644 index 000000000..9ffb53a7a --- /dev/null +++ b/src/main/java/subway/domain/fare/distance/BasicFarePolicy.java @@ -0,0 +1,15 @@ +package subway.domain.fare.distance; + +import org.springframework.stereotype.Component; +import subway.domain.fare.DistancePolicy; + +@Component +public class BasicFarePolicy implements DistancePolicy { + private static final int BASIC_FARE = 1250; + + @Override + public int calculate(final int distance) { + return BASIC_FARE; + } + +} diff --git a/src/main/java/subway/domain/fare/distance/EightUnitFarePolicy.java b/src/main/java/subway/domain/fare/distance/EightUnitFarePolicy.java new file mode 100644 index 000000000..72dcaeae4 --- /dev/null +++ b/src/main/java/subway/domain/fare/distance/EightUnitFarePolicy.java @@ -0,0 +1,19 @@ +package subway.domain.fare.distance; + +import org.springframework.stereotype.Component; +import subway.domain.fare.DistancePolicy; + +@Component +public class EightUnitFarePolicy implements DistancePolicy { + private static final int UNIT = 8; + private static final int FARE = 100; + private static final int MIN_CALCULATE_RANGE = 50; + + @Override + public int calculate(final int distance) { + if (distance <= MIN_CALCULATE_RANGE) { + return 0; + } + return (int) ((Math.ceil((distance - MIN_CALCULATE_RANGE - 1) / UNIT) + 1) * FARE); + } +} diff --git a/src/main/java/subway/domain/fare/distance/FiveUnitFarePolicy.java b/src/main/java/subway/domain/fare/distance/FiveUnitFarePolicy.java new file mode 100644 index 000000000..09297d13a --- /dev/null +++ b/src/main/java/subway/domain/fare/distance/FiveUnitFarePolicy.java @@ -0,0 +1,23 @@ +package subway.domain.fare.distance; + +import org.springframework.stereotype.Component; +import subway.domain.fare.DistancePolicy; + +@Component +public class FiveUnitFarePolicy implements DistancePolicy { + private static final int UNIT = 5; + private static final int FARE = 100; + private static final int MIN_CALCULATE_RANGE = 10; + private static final int MAX_CALCULATE_RANGE = 50; + + @Override + public int calculate(final int distance) { + if (distance >= MAX_CALCULATE_RANGE) { + return (int) ((Math.ceil((MAX_CALCULATE_RANGE - MIN_CALCULATE_RANGE - 1) / UNIT) + 1) * FARE); + } + if (distance <= MIN_CALCULATE_RANGE) { + return 0; + } + return (int) ((Math.ceil((distance - MIN_CALCULATE_RANGE - 1) / UNIT) + 1) * FARE); + } +} diff --git a/src/main/java/subway/domain/graph/Path.java b/src/main/java/subway/domain/graph/Path.java new file mode 100644 index 000000000..a418fbb39 --- /dev/null +++ b/src/main/java/subway/domain/graph/Path.java @@ -0,0 +1,29 @@ +package subway.domain.graph; + +import org.jgrapht.graph.DefaultWeightedEdge; +import subway.domain.Section; +import subway.domain.Station; + +public class Path extends DefaultWeightedEdge { + private final Section section; + + public Path(final Section section) { + this.section = section; + } + + @Override + protected double getWeight() { + return section.getDistance().getValue(); + } + + @Override + protected Station getSource() { + return section.getUp(); + } + + @Override + protected Station getTarget() { + return section.getDown(); + } + +} diff --git a/src/main/java/subway/domain/graph/Paths.java b/src/main/java/subway/domain/graph/Paths.java new file mode 100644 index 000000000..3ded1def9 --- /dev/null +++ b/src/main/java/subway/domain/graph/Paths.java @@ -0,0 +1,31 @@ +package subway.domain.graph; + +import org.jgrapht.WeightedGraph; +import org.jgrapht.graph.DefaultWeightedEdge; +import subway.domain.Lines; +import subway.domain.Section; +import subway.domain.Station; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class Paths { + private final List paths; + + private Paths(final List paths) { + this.paths = new ArrayList<>(paths); + } + + public static Paths from(final Lines lines) { + final List
sections = lines.getAllSections(); + final List paths = sections.stream() + .map(Path::new) + .collect(Collectors.toList()); + return new Paths(paths); + } + + public void addAllToGraph(final WeightedGraph graph) { + paths.forEach(path -> graph.addEdge(path.getSource(), path.getTarget(), path)); + } +} diff --git a/src/main/java/subway/domain/graph/SubwayConfig.java b/src/main/java/subway/domain/graph/SubwayConfig.java new file mode 100644 index 000000000..bc9c91972 --- /dev/null +++ b/src/main/java/subway/domain/graph/SubwayConfig.java @@ -0,0 +1,14 @@ +package subway.domain.graph; + +import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.WeightedMultigraph; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SubwayConfig { + @Bean + public SubwayGraph subwayGraph() { + return new SubwayGraph(new WeightedMultigraph<>(DefaultWeightedEdge.class)); + } +} diff --git a/src/main/java/subway/domain/graph/SubwayGraph.java b/src/main/java/subway/domain/graph/SubwayGraph.java new file mode 100644 index 000000000..fc3b8e0bb --- /dev/null +++ b/src/main/java/subway/domain/graph/SubwayGraph.java @@ -0,0 +1,69 @@ +package subway.domain.graph; + +import org.jgrapht.WeightedGraph; +import org.jgrapht.alg.interfaces.ShortestPathAlgorithm; +import org.jgrapht.alg.shortestpath.DijkstraShortestPath; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.WeightedMultigraph; +import subway.domain.Lines; +import subway.domain.Station; + +import java.util.List; +import java.util.stream.Collectors; + +public class SubwayGraph { + private WeightedGraph subwayGraph; + private ShortestPathAlgorithm shortestPath; + + public SubwayGraph(final WeightedGraph graph) { + this.subwayGraph = graph; + } + + public static SubwayGraph from(final Lines lines) { + final List stations = lines.getAllStations(); + final Paths paths = Paths.from(lines); + + final WeightedMultigraph graph = + new WeightedMultigraph<>(DefaultWeightedEdge.class); + stations.forEach(graph::addVertex); + paths.addAllToGraph(graph); + return new SubwayGraph(graph); + } + + public void update(final Lines lines) { + final List stations = lines.getAllStations(); + final Paths paths = Paths.from(lines); + + final WeightedGraph graph = + new WeightedMultigraph<>(DefaultWeightedEdge.class); + stations.forEach(graph::addVertex); + paths.addAllToGraph(graph); + + this.subwayGraph = graph; + this.shortestPath = new DijkstraShortestPath<>(subwayGraph); + } + + public List findPath(final Station source, final Station sink) { + if (shortestPath == null) { + shortestPath = new DijkstraShortestPath<>(subwayGraph); + } + return shortestPath.getPath(source, sink).getVertexList(); + } + + public int calculateDistanceSum(final Station source, final Station sink) { + return calculateDistance(source, sink).stream() + .mapToInt(Double::intValue) + .sum(); + } + + private List calculateDistance(final Station source, final Station sink) { + final List edgeList = shortestPath.getPath(source, sink).getEdgeList(); + return mapToWeight(edgeList); + } + + private List mapToWeight(final List edgeList) { + return edgeList.stream() + .map(edge -> ((Path) edge).getWeight()) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/subway/dto/CreateType.java b/src/main/java/subway/dto/CreateType.java deleted file mode 100644 index aaf64c61f..000000000 --- a/src/main/java/subway/dto/CreateType.java +++ /dev/null @@ -1,14 +0,0 @@ -package subway.dto; - -public enum CreateType { - INIT("init"), - UP("up"), - DOWN("down"), - MID("mid"); - - private final String value; - - CreateType(String value) { - this.value = value; - } -} diff --git a/src/main/java/subway/dto/LineRequest.java b/src/main/java/subway/dto/LineRequest.java deleted file mode 100644 index 16cb5bf76..000000000 --- a/src/main/java/subway/dto/LineRequest.java +++ /dev/null @@ -1,23 +0,0 @@ -package subway.dto; - -public class LineRequest { - private String name; - private String color; - - public LineRequest() { - } - - public LineRequest(String name, String color) { - this.name = name; - this.color = color; - } - - public String getName() { - return name; - } - - public String getColor() { - return color; - } - -} diff --git a/src/main/java/subway/dto/StationRequest.java b/src/main/java/subway/dto/StationRequest.java deleted file mode 100644 index 4f9b93a8c..000000000 --- a/src/main/java/subway/dto/StationRequest.java +++ /dev/null @@ -1,84 +0,0 @@ -package subway.dto; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class StationRequest { - @JsonProperty("name") - private String name; - @JsonProperty("create_type") - private String createType; - @JsonProperty("line") - private String line; - @JsonProperty("up_endpoint") - private boolean upEndpoint; - @JsonProperty("down_endpoint") - private boolean downEndpoint; - @JsonProperty("previous_station") - private String previousStation; - @JsonProperty("previous_distance") - private Integer previousDistance; - @JsonProperty("next_station") - private String nextStation; - @JsonProperty("next_distance") - private Integer nextDistance; - - public StationRequest(String name, String createType, String line, boolean upEndpoint, boolean downEndpoint, String previousStation, String nextStation, Integer previousDistance, Integer nextDistance) { - this.name = name; - this.createType = createType; - this.line = line; - this.upEndpoint = upEndpoint; - this.downEndpoint = downEndpoint; - this.previousStation = previousStation; - this.nextStation = nextStation; - this.previousDistance = previousDistance; - this.nextDistance = nextDistance; - } - -// public StationRequest(String name, CreateType createType, String line, boolean upEndpoint, boolean downEndpoint, String previousStation, String nextStation, Integer previousDistance, Integer nextDistance) { -// this.name = name; -// this.createType = createType; -// this.line = line; -// this.upEndpoint = upEndpoint; -// this.downEndpoint = downEndpoint; -// this.previousStation = previousStation; -// this.nextStation = nextStation; -// this.previousDistance = previousDistance; -// this.nextDistance = nextDistance; -// } - - public String getName() { - return name; - } - - public String getCreateType() { - return createType; - } - - public String getLine() { - return line; - } - - public boolean isUpEndpoint() { - return upEndpoint; - } - - public boolean isDownEndpoint() { - return downEndpoint; - } - - public String getPreviousStation() { - return previousStation; - } - - public String getNextStation() { - return nextStation; - } - - public Integer getPreviousDistance() { - return previousDistance; - } - - public Integer getNextDistance() { - return nextDistance; - } -} diff --git a/src/main/java/subway/dto/request/ConnectionEndpointRequest.java b/src/main/java/subway/dto/request/ConnectionEndpointRequest.java new file mode 100644 index 000000000..619217bc7 --- /dev/null +++ b/src/main/java/subway/dto/request/ConnectionEndpointRequest.java @@ -0,0 +1,27 @@ +package subway.dto.request; + +import subway.ui.EndpointType; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Positive; + +public class ConnectionEndpointRequest { + @NotNull + private final String endpointType; + @Positive + @NotNull + private final int distance; + + public ConnectionEndpointRequest(final String connectType, final int distance) { + this.endpointType = connectType; + this.distance = distance; + } + + public EndpointType getEndpointType() { + return EndpointType.fromValue(endpointType); + } + + public int getDistance() { + return distance; + } +} diff --git a/src/main/java/subway/dto/request/ConnectionInitRequest.java b/src/main/java/subway/dto/request/ConnectionInitRequest.java new file mode 100644 index 000000000..840e023ff --- /dev/null +++ b/src/main/java/subway/dto/request/ConnectionInitRequest.java @@ -0,0 +1,26 @@ +package subway.dto.request; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Positive; + +public class ConnectionInitRequest { + @Positive + @NotNull + private final Long nextStationId; + @Positive + @NotNull + private final int distance; + + public ConnectionInitRequest(final Long nextStationId, final int distance) { + this.nextStationId = nextStationId; + this.distance = distance; + } + + public Long getNextStationId() { + return nextStationId; + } + + public int getDistance() { + return distance; + } +} diff --git a/src/main/java/subway/dto/request/ConnectionMidRequest.java b/src/main/java/subway/dto/request/ConnectionMidRequest.java new file mode 100644 index 000000000..7e537e786 --- /dev/null +++ b/src/main/java/subway/dto/request/ConnectionMidRequest.java @@ -0,0 +1,26 @@ +package subway.dto.request; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Positive; + +public class ConnectionMidRequest { + @Positive + @NotNull + private final Long prevStationId; + @Positive + @NotNull + private final int distance; + + public ConnectionMidRequest(final Long prevStationId, final int distance) { + this.prevStationId = prevStationId; + this.distance = distance; + } + + public Long getPrevStationId() { + return prevStationId; + } + + public int getDistance() { + return distance; + } +} diff --git a/src/main/java/subway/dto/request/LineRequest.java b/src/main/java/subway/dto/request/LineRequest.java new file mode 100644 index 000000000..9dee6ce6a --- /dev/null +++ b/src/main/java/subway/dto/request/LineRequest.java @@ -0,0 +1,34 @@ +package subway.dto.request; + +import subway.domain.Line; +import subway.domain.Sections; + +import javax.validation.constraints.NotEmpty; + +public class LineRequest { + @NotEmpty + private String name; + @NotEmpty + private String color; + + public LineRequest() { + } + + public LineRequest(final String name, final String color) { + this.name = name; + this.color = color; + } + + public Line toLine() { + return new Line(null, name, color, new Sections()); + } + + public String getName() { + return name; + } + + public String getColor() { + return color; + } + +} diff --git a/src/main/java/subway/dto/request/StationRequest.java b/src/main/java/subway/dto/request/StationRequest.java new file mode 100644 index 000000000..10bc4564d --- /dev/null +++ b/src/main/java/subway/dto/request/StationRequest.java @@ -0,0 +1,19 @@ +package subway.dto.request; + +import javax.validation.constraints.NotEmpty; + +public class StationRequest { + @NotEmpty + private String name; + + public StationRequest() { + } + + public StationRequest(final String name) { + this.name = name; + } + + public String getName() { + return name; + } +} \ No newline at end of file diff --git a/src/main/java/subway/dto/LineResponse.java b/src/main/java/subway/dto/response/LineResponse.java similarity index 61% rename from src/main/java/subway/dto/LineResponse.java rename to src/main/java/subway/dto/response/LineResponse.java index c9b668122..bad36e95f 100644 --- a/src/main/java/subway/dto/LineResponse.java +++ b/src/main/java/subway/dto/response/LineResponse.java @@ -1,19 +1,19 @@ -package subway.dto; +package subway.dto.response; import subway.domain.Line; public class LineResponse { - private Long id; - private String name; - private String color; + private final Long id; + private final String name; + private final String color; - public LineResponse(Long id, String name, String color) { + public LineResponse(final Long id, final String name, final String color) { this.id = id; this.name = name; this.color = color; } - public static LineResponse of(Line line) { + public static LineResponse of(final Line line) { return new LineResponse(line.getId(), line.getName(), line.getColor()); } diff --git a/src/main/java/subway/dto/response/LineStationResponse.java b/src/main/java/subway/dto/response/LineStationResponse.java new file mode 100644 index 000000000..bbf01d4dc --- /dev/null +++ b/src/main/java/subway/dto/response/LineStationResponse.java @@ -0,0 +1,21 @@ +package subway.dto.response; + +import java.util.List; + +public class LineStationResponse { + private final List stationResponses; + private final List distances; + + public LineStationResponse(final List stations, final List distances) { + this.stationResponses = stations; + this.distances = distances; + } + + public List getStationResponses() { + return stationResponses; + } + + public List getDistances() { + return distances; + } +} diff --git a/src/main/java/subway/dto/response/PathResponse.java b/src/main/java/subway/dto/response/PathResponse.java new file mode 100644 index 000000000..cf4096b4b --- /dev/null +++ b/src/main/java/subway/dto/response/PathResponse.java @@ -0,0 +1,27 @@ +package subway.dto.response; + +import java.util.List; + +public class PathResponse { + private final List stations; + private final int distance; + private final int fare; + + public PathResponse(final List stations, final int distance, final int fare) { + this.stations = stations; + this.distance = distance; + this.fare = fare; + } + + public List getStations() { + return stations; + } + + public int getDistance() { + return distance; + } + + public int getFare() { + return fare; + } +} diff --git a/src/main/java/subway/dto/StationResponse.java b/src/main/java/subway/dto/response/StationResponse.java similarity index 59% rename from src/main/java/subway/dto/StationResponse.java rename to src/main/java/subway/dto/response/StationResponse.java index 5eec02fe0..21e898b43 100644 --- a/src/main/java/subway/dto/StationResponse.java +++ b/src/main/java/subway/dto/response/StationResponse.java @@ -1,17 +1,17 @@ -package subway.dto; +package subway.dto.response; import subway.domain.Station; public class StationResponse { - private Long id; - private String name; + private final Long id; + private final String name; - public StationResponse(Long id, String name) { + public StationResponse(final Long id, final String name) { this.id = id; this.name = name; } - public static StationResponse of(Station station) { + public static StationResponse of(final Station station) { return new StationResponse(station.getId(), station.getName()); } diff --git a/src/main/java/subway/exceptions/IllegalDistanceException.java b/src/main/java/subway/exceptions/IllegalDistanceException.java new file mode 100644 index 000000000..0cb805390 --- /dev/null +++ b/src/main/java/subway/exceptions/IllegalDistanceException.java @@ -0,0 +1,7 @@ +package subway.exceptions; + +public class IllegalDistanceException extends RuntimeException { + public IllegalDistanceException(final String message) { + super(message); + } +} diff --git a/src/main/java/subway/exceptions/IllegalStationException.java b/src/main/java/subway/exceptions/IllegalStationException.java new file mode 100644 index 000000000..4b8cb3b3e --- /dev/null +++ b/src/main/java/subway/exceptions/IllegalStationException.java @@ -0,0 +1,7 @@ +package subway.exceptions; + +public class IllegalStationException extends RuntimeException { + public IllegalStationException(final String message) { + super(message); + } +} diff --git a/src/main/java/subway/exceptions/SectionStateException.java b/src/main/java/subway/exceptions/SectionStateException.java new file mode 100644 index 000000000..fd12286f4 --- /dev/null +++ b/src/main/java/subway/exceptions/SectionStateException.java @@ -0,0 +1,7 @@ +package subway.exceptions; + +public class SectionStateException extends RuntimeException { + public SectionStateException(final String message) { + super(message); + } +} diff --git a/src/main/java/subway/persistance/LineDao.java b/src/main/java/subway/persistance/LineDao.java new file mode 100644 index 000000000..03a8b009b --- /dev/null +++ b/src/main/java/subway/persistance/LineDao.java @@ -0,0 +1,61 @@ +package subway.persistance; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.simple.SimpleJdbcInsert; +import org.springframework.stereotype.Repository; +import subway.domain.Line; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +@Repository +public class LineDao { + private final JdbcTemplate jdbcTemplate; + private final SimpleJdbcInsert insertAction; + + private final RowMapper rowMapper = (rs, rowNum) -> + new Line( + rs.getLong("line_id"), + rs.getString("name"), + rs.getString("color") + ); + + public LineDao(final JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + this.insertAction = new SimpleJdbcInsert(jdbcTemplate) + .withTableName("line") + .usingGeneratedKeyColumns("line_id"); + } + + public Line insert(final Line line) { + final Map params = new HashMap<>(); + params.put("line_id", line.getId()); + params.put("name", line.getName()); + params.put("color", line.getColor()); + + final Long lineId = insertAction.executeAndReturnKey(params).longValue(); + return new Line(lineId, line.getName(), line.getColor()); + } + + public void update(final Line line) { + final String sql = "UPDATE LINE SET name = ?, color = ? WHERE line_id = ?"; + jdbcTemplate.update(sql, line.getName(), line.getColor(), line.getId()); + } + + public List findAll() { + final String sql = "select line_id, name, color from LINE"; + return jdbcTemplate.query(sql, rowMapper); + } + + public Optional findById(final Long id) { + final String sql = "select line_id, name, color from LINE WHERE line_id = ?"; + return Optional.ofNullable(jdbcTemplate.queryForObject(sql, rowMapper, id)); + } + + public void deleteById(final Long id) { + jdbcTemplate.update("delete from Line where line_id = ?", id); + } +} \ No newline at end of file diff --git a/src/main/java/subway/persistance/LineRepositoryImpl.java b/src/main/java/subway/persistance/LineRepositoryImpl.java new file mode 100644 index 000000000..4fb75d7f9 --- /dev/null +++ b/src/main/java/subway/persistance/LineRepositoryImpl.java @@ -0,0 +1,101 @@ +package subway.persistance; + +import org.springframework.stereotype.Repository; +import subway.domain.Distance; +import subway.domain.Line; +import subway.domain.LineRepository; +import subway.domain.Lines; +import subway.domain.Section; +import subway.domain.Sections; +import subway.domain.Station; +import subway.persistance.entity.SectionEntity; + +import java.util.Comparator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +@Repository +public class LineRepositoryImpl implements LineRepository { + private final LineDao lineDao; + private final SectionDao sectionDao; + private final StationDao stationDao; + + public LineRepositoryImpl(final LineDao lineDao, final SectionDao sectionDao, final StationDao stationDao) { + this.lineDao = lineDao; + this.sectionDao = sectionDao; + this.stationDao = stationDao; + } + + @Override + public Optional findById(final Long id) { + try { + final Line line = lineDao.findById(id) + .orElseThrow(NoSuchElementException::new); + + final List sectionEntities = sectionDao.findByLineId(line.getId()); + final List
sections = mapToSections(sectionEntities); + + return Optional.of(new Line(line.getId(), line.getName(), line.getColor(), new Sections(sections))); + } catch (final NoSuchElementException e) { + return Optional.empty(); + } + } + + private List
mapToSections(final List sectionEntities) { + return sectionEntities.stream() + .sorted(Comparator.comparingInt(SectionEntity::getListOrder)) + .map(this::entityToSection) + .collect(Collectors.toList()); + } + + private Section entityToSection(final SectionEntity sectionEntity) { + final Station upStation = stationDao.findById(sectionEntity.getUpStationId()).get(); + final Station downStation = stationDao.findById(sectionEntity.getDownStationId()).get(); + return new Section(upStation, downStation, new Distance(sectionEntity.getDistance())); + } + + @Override + public Lines findAll() { + final List lines = lineDao.findAll().stream() + .map(line -> findById(line.getId()) + .orElseThrow(() -> new RuntimeException("라인을 불러오는 데 실패했습니다."))) + .collect(Collectors.toList()); + return new Lines(lines); + } + + @Override + public Line save(final Line line) { + final Line insertedLine = updateIfExistOrElseInsert(line); + + sectionDao.deleteByLineId(line.getId()); + + final List
sections = line.getSections(); + final List sectionEntities = mapToSectionEntities(insertedLine, sections); + sectionEntities.forEach(sectionDao::insertSection); + + return new Line(insertedLine.getId(), line.getName(), line.getColor(), new Sections(sections)); + } + + private Line updateIfExistOrElseInsert(final Line line) { + if (line.getId() == null) { + return lineDao.insert(line); + } + lineDao.update(line); + return line; + } + + private List mapToSectionEntities(final Line insertedLine, final List
sections) { + return IntStream.range(0, sections.size()) + .mapToObj(i -> new SectionEntity(sections.get(i), insertedLine.getId(), i)) + .collect(Collectors.toList()); + } + + @Override + public void delete(final Line line) { + sectionDao.deleteByLineId(line.getId()); + lineDao.deleteById(line.getId()); + } +} diff --git a/src/main/java/subway/persistance/SectionDao.java b/src/main/java/subway/persistance/SectionDao.java new file mode 100644 index 000000000..2a8077ca5 --- /dev/null +++ b/src/main/java/subway/persistance/SectionDao.java @@ -0,0 +1,52 @@ +package subway.persistance; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; +import subway.persistance.entity.SectionEntity; + +import java.util.List; + +@Repository +public class SectionDao { + + private final JdbcTemplate jdbcTemplate; + + private final RowMapper sectionEntityRowMapper = (rs, rowNum) -> { + Long sectionId = rs.getLong("section_id"); + Long upStationId = rs.getLong("up_station_id"); + Long downStationId = rs.getLong("down_station_id"); + int distance = rs.getInt("distance"); + Long lineId = rs.getLong("line_id"); + int listOrder = rs.getInt("list_order"); + return new SectionEntity(sectionId, upStationId, downStationId, distance, lineId, listOrder); + }; + + public SectionDao(final JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + public void insertSection(final SectionEntity entity) { + final String sql = "INSERT INTO section(up_station_id, down_station_id, distance, line_id, list_order) VALUES (?, ?, ?, ?, ?)"; + + jdbcTemplate.update(sql, entity.getUpStationId(), entity.getDownStationId(), entity.getDistance(), entity.getLineId(), entity.getListOrder()); + } + + public List findByLineId(final Long lineId) { + final String sql = "SELECT " + + "section_id, " + + "up_station_id, " + + "down_station_id," + + " distance, line_id, " + + "list_order " + + "FROM section WHERE line_id = ?"; + + return jdbcTemplate.query(sql, sectionEntityRowMapper, lineId); + } + + public void deleteByLineId(final Long lineId) { + final String sql = "DELETE FROM section WHERE line_id = ?"; + + jdbcTemplate.update(sql, lineId); + } +} diff --git a/src/main/java/subway/dao/StationDao.java b/src/main/java/subway/persistance/StationDao.java similarity index 66% rename from src/main/java/subway/dao/StationDao.java rename to src/main/java/subway/persistance/StationDao.java index bb126d29b..96ab7125f 100644 --- a/src/main/java/subway/dao/StationDao.java +++ b/src/main/java/subway/persistance/StationDao.java @@ -1,5 +1,6 @@ -package subway.dao; +package subway.persistance; +import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource; @@ -9,6 +10,7 @@ import subway.domain.Station; import java.util.List; +import java.util.Optional; @Repository public class StationDao { @@ -17,16 +19,15 @@ public class StationDao { private final RowMapper rowMapper = (rs, rowNum) -> new Station( - rs.getLong("id"), + rs.getLong("station_id"), rs.getString("name") ); - public StationDao(final JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; this.insertAction = new SimpleJdbcInsert(jdbcTemplate) .withTableName("station") - .usingGeneratedKeyColumns("id"); + .usingGeneratedKeyColumns("station_id"); } public Station insert(final Station station) { @@ -36,32 +37,26 @@ public Station insert(final Station station) { } public List findAll() { - final String sql = "select * from STATION"; + final String sql = "select station_id, name from STATION"; return jdbcTemplate.query(sql, rowMapper); } - public Station findById(final Long id) { - final String sql = "select * from STATION where id = ?"; - return jdbcTemplate.queryForObject(sql, rowMapper, id); + public Optional findById(final Long id) { + final String sql = "select station_id, name from STATION where station_id = ?"; + try { + return Optional.of(jdbcTemplate.queryForObject(sql, rowMapper, id)); + } catch (final EmptyResultDataAccessException e) { + return Optional.empty(); + } } public void update(final Station newStation) { - final String sql = "update STATION set name = ? where id = ?"; + final String sql = "update STATION set name = ? where station_id = ?"; jdbcTemplate.update(sql, newStation.getName(), newStation.getId()); } public void deleteById(final Long id) { - final String sql = "delete from STATION where id = ?"; + final String sql = "delete from STATION where station_id = ?"; jdbcTemplate.update(sql, id); } - - public Station findByName(final String name) { - final String sql = "select * from STATION where name = ?"; - return jdbcTemplate.queryForObject(sql, rowMapper, name); - } - - public void deleteAll() { - final String sql = "delete from STATION"; - jdbcTemplate.update(sql); - } } diff --git a/src/main/java/subway/persistance/entity/SectionEntity.java b/src/main/java/subway/persistance/entity/SectionEntity.java new file mode 100644 index 000000000..28a067daf --- /dev/null +++ b/src/main/java/subway/persistance/entity/SectionEntity.java @@ -0,0 +1,49 @@ +package subway.persistance.entity; + +import subway.domain.Section; + +public class SectionEntity { + private final Long sectionId; + private final Long upStationId; + private final Long downStationId; + private final int distance; + private final Long lineId; + private final int listOrder; + + public SectionEntity(final Long sectionId, final Long upStationId, final Long downStationId, final int distance, final Long lineId, final int listOrder) { + this.sectionId = sectionId; + this.upStationId = upStationId; + this.downStationId = downStationId; + this.distance = distance; + this.lineId = lineId; + this.listOrder = listOrder; + } + + public SectionEntity(final Section section, final Long lineId, final int listOrder) { + this(null, section.getUp().getId(), section.getDown().getId(), section.getDistance().getValue(), lineId, listOrder); + } + + public Long getSectionId() { + return sectionId; + } + + public Long getUpStationId() { + return upStationId; + } + + public Long getDownStationId() { + return downStationId; + } + + public int getDistance() { + return distance; + } + + public Long getLineId() { + return lineId; + } + + public int getListOrder() { + return listOrder; + } +} diff --git a/src/main/java/subway/ui/EndpointType.java b/src/main/java/subway/ui/EndpointType.java new file mode 100644 index 000000000..d23840054 --- /dev/null +++ b/src/main/java/subway/ui/EndpointType.java @@ -0,0 +1,30 @@ +package subway.ui; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import java.util.Arrays; + +public enum EndpointType { + UP("up"), + DOWN("down"); + + private final String value; + + EndpointType(final String value) { + this.value = value; + } + + @JsonCreator + public static EndpointType fromValue(final String value) { + return Arrays.stream(values()) + .filter(type -> type.value.equals(value)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("잘못된 종점 타입입니다.")); + } + + @JsonValue + public String getValue() { + return value; + } +} diff --git a/src/main/java/subway/ui/LineController.java b/src/main/java/subway/ui/LineController.java index 44293e807..3c9c98b08 100644 --- a/src/main/java/subway/ui/LineController.java +++ b/src/main/java/subway/ui/LineController.java @@ -1,13 +1,20 @@ package subway.ui; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import subway.application.LineService; -import subway.dto.LineRequest; -import subway.dto.LineResponse; +import subway.dto.request.LineRequest; +import subway.dto.response.LineResponse; +import javax.validation.Valid; import java.net.URI; -import java.sql.SQLException; import java.util.List; @RestController @@ -21,8 +28,8 @@ public LineController(final LineService lineService) { } @PostMapping - public ResponseEntity createLine(@RequestBody LineRequest lineRequest) { - LineResponse line = lineService.saveLine(lineRequest); + public ResponseEntity createLine(@RequestBody @Valid final LineRequest lineRequest) { + final LineResponse line = lineService.createLine(lineRequest); return ResponseEntity.created(URI.create("/lines/" + line.getId())).body(line); } @@ -37,7 +44,7 @@ public ResponseEntity findLineById(@PathVariable final Long id) { } @PutMapping("/{id}") - public ResponseEntity updateLine(@PathVariable final Long id, @RequestBody final LineRequest lineUpdateRequest) { + public ResponseEntity updateLine(@PathVariable final Long id, @RequestBody @Valid final LineRequest lineUpdateRequest) { lineService.updateLine(id, lineUpdateRequest); return ResponseEntity.ok().build(); } @@ -48,8 +55,4 @@ public ResponseEntity deleteLine(@PathVariable final Long id) { return ResponseEntity.noContent().build(); } - @ExceptionHandler(SQLException.class) - public ResponseEntity handleSQLException() { - return ResponseEntity.badRequest().build(); - } } diff --git a/src/main/java/subway/ui/LineStationController.java b/src/main/java/subway/ui/LineStationController.java new file mode 100644 index 000000000..301b88a22 --- /dev/null +++ b/src/main/java/subway/ui/LineStationController.java @@ -0,0 +1,75 @@ +package subway.ui; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import subway.application.LineStationService; +import subway.dto.request.ConnectionEndpointRequest; +import subway.dto.request.ConnectionInitRequest; +import subway.dto.request.ConnectionMidRequest; +import subway.dto.response.LineStationResponse; + +import javax.validation.Valid; +import java.util.List; + +@RestController +@RequestMapping("/lines") +public class LineStationController { + private final LineStationService lineStationService; + + public LineStationController(final LineStationService lineStationService) { + this.lineStationService = lineStationService; + } + + @PatchMapping("/{lineId}/stations/{stationId}/init") + public ResponseEntity addInitStation( + @PathVariable final Long lineId, + @PathVariable final Long stationId, + @RequestBody @Valid final ConnectionInitRequest request) { + lineStationService.addInitStations(lineId, stationId, request.getNextStationId(), request.getDistance()); + + return ResponseEntity.noContent().build(); + } + + @PatchMapping("/{lineId}/stations/{stationId}/endpoint") + public ResponseEntity addEndpointStation( + @PathVariable final Long lineId, + @PathVariable final Long stationId, + @RequestBody @Valid final ConnectionEndpointRequest request) { + lineStationService.addEndpoint(request.getEndpointType(), lineId, stationId, request.getDistance()); + return ResponseEntity.noContent().build(); + } + + @PatchMapping("/{lineId}/stations/{stationId}/mid") + public ResponseEntity addMidStation( + @PathVariable final Long lineId, + @PathVariable final Long stationId, + @RequestBody @Valid final ConnectionMidRequest request) { + lineStationService.addIntermediate(lineId, stationId, request.getPrevStationId(), request.getDistance()); + return ResponseEntity.noContent().build(); + } + + + @DeleteMapping("/{lineId}/stations/{stationId}") + public ResponseEntity deleteStationById( + @PathVariable final Long lineId, + @PathVariable final Long stationId) { + lineStationService.deleteStationInLine(lineId, stationId); + return ResponseEntity.noContent().build(); + } + + @GetMapping("/{lineId}/stations") + public ResponseEntity showStationsByLineId(@PathVariable final Long lineId) { + return ResponseEntity.ok().body(lineStationService.findByLineId(lineId)); + } + + @GetMapping("/stations") + public ResponseEntity> showStations() { + return ResponseEntity.ok().body(lineStationService.findAll()); + } +} diff --git a/src/main/java/subway/ui/PathController.java b/src/main/java/subway/ui/PathController.java new file mode 100644 index 000000000..1ecdeb8ff --- /dev/null +++ b/src/main/java/subway/ui/PathController.java @@ -0,0 +1,25 @@ +package subway.ui; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import subway.application.PathService; +import subway.dto.response.PathResponse; + +@RestController +@RequestMapping("/path") +public class PathController { + + private final PathService pathService; + + public PathController(final PathService pathService) { + this.pathService = pathService; + } + + @GetMapping + public ResponseEntity findShortestPath(@RequestParam final Long source, @RequestParam final Long target) { + return ResponseEntity.ok().body(pathService.findShortestPath(source, target)); + } +} diff --git a/src/main/java/subway/ui/StationController.java b/src/main/java/subway/ui/StationController.java index 244e1adbe..788866f2a 100644 --- a/src/main/java/subway/ui/StationController.java +++ b/src/main/java/subway/ui/StationController.java @@ -1,26 +1,38 @@ package subway.ui; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import subway.application.LineStationService; import subway.application.StationService; -import subway.dto.StationRequest; -import subway.dto.StationResponse; +import subway.dto.request.StationRequest; +import subway.dto.response.StationResponse; +import javax.validation.Valid; +import java.net.URI; import java.util.List; @RestController @RequestMapping("/stations") public class StationController { private final StationService stationService; + private final LineStationService lineStationService; - public StationController(final StationService stationService) { + public StationController(final StationService stationService, final LineStationService lineStationService) { this.stationService = stationService; + this.lineStationService = lineStationService; } @PostMapping - public ResponseEntity createStation(@RequestBody final List stationRequests) { - stationService.saveStation(stationRequests); - return ResponseEntity.ok().build(); + public ResponseEntity createStation(@RequestBody @Valid final StationRequest stationRequest) { + final StationResponse stationResponse = stationService.saveStation(stationRequest); + return ResponseEntity.created(URI.create("/stations/" + stationResponse.getId())).build(); } @GetMapping @@ -28,31 +40,21 @@ public ResponseEntity> showStations() { return ResponseEntity.ok().body(stationService.findAllStationResponses()); } - @GetMapping("/lines/{id}") - public ResponseEntity> showStationsByLineId(@PathVariable final Long id) { - return ResponseEntity.ok().body(stationService.findStationsByLineId(id)); - } - @GetMapping("/{id}") public ResponseEntity showStation(@PathVariable final Long id) { return ResponseEntity.ok().body(stationService.findStationResponseById(id)); } @PutMapping("/{id}") - public ResponseEntity updateStation(@PathVariable final Long id, @RequestBody final StationRequest stationRequest) { + public ResponseEntity updateStation(@PathVariable final Long id, @RequestBody @Valid final StationRequest stationRequest) { stationService.updateStation(id, stationRequest); return ResponseEntity.ok().build(); } @DeleteMapping("/{id}") public ResponseEntity deleteStation(@PathVariable final Long id) { - stationService.deleteStationById(id); + lineStationService.deleteStation(id); return ResponseEntity.noContent().build(); } - @ExceptionHandler - public ResponseEntity handleSQLException(Exception e) { - e.printStackTrace(); - return ResponseEntity.badRequest().build(); - } } diff --git a/src/main/java/subway/ui/exceptionHandler/GlobalExceptionHandler.java b/src/main/java/subway/ui/exceptionHandler/GlobalExceptionHandler.java new file mode 100644 index 000000000..fa2eff214 --- /dev/null +++ b/src/main/java/subway/ui/exceptionHandler/GlobalExceptionHandler.java @@ -0,0 +1,44 @@ +package subway.ui.exceptionHandler; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import subway.exceptions.IllegalDistanceException; +import subway.exceptions.IllegalStationException; +import subway.exceptions.SectionStateException; + +@RestControllerAdvice +public class GlobalExceptionHandler { + private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); + + @ExceptionHandler({IllegalDistanceException.class, IllegalStationException.class, SectionStateException.class}) + public ResponseEntity handleCustomExceptions(final Exception e) { + logger.debug(e.getMessage()); + return ResponseEntity.badRequest().body(e.getMessage()); + } + + @ExceptionHandler({DataAccessException.class, EmptyResultDataAccessException.class}) + public ResponseEntity handleSQLException(final Exception e) { + logger.debug(e.getMessage()); + return ResponseEntity.badRequest().build(); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleMethodArgumentNotValidException(final Exception e) { + logger.debug(e.getMessage()); + return ResponseEntity.badRequest().body(e.getMessage()); + } + + @ExceptionHandler(Exception.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public void handleAll(final Exception e) { + logger.error(e.getMessage()); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 000000000..0b570c2e8 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,5 @@ +spring.datasource.url=jdbc:mysql://localhost:13306/subway?characterEncoding=UTF-8&serverTimezone=Asia/Seoul +spring.datasource.username=maco +spring.datasource.password=maco +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.sql.init.mode=always diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml deleted file mode 100644 index f88eb5b24..000000000 --- a/src/main/resources/application.yml +++ /dev/null @@ -1,5 +0,0 @@ -spring: - datasource: - url: jdbc:h2:mem:testdb;MODE=MySQL - driver-class-name: org.h2.Driver - h2.console.enabled: true diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 000000000..531b1eb46 --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,23 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 5ae8598ad..e429c4cfc 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -1,28 +1,27 @@ CREATE TABLE IF NOT EXISTS station ( - id BIGINT AUTO_INCREMENT NOT NULL, + station_id BIGINT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL UNIQUE, - PRIMARY KEY (id) + PRIMARY KEY (station_id) ); CREATE TABLE IF NOT EXISTS line ( - id BIGINT AUTO_INCREMENT NOT NULL, + line_id BIGINT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL UNIQUE, color VARCHAR(20) NOT NULL, - up_endpoint_id BIGINT, - down_endpoint_id BIGINT, - PRIMARY KEY (id) + PRIMARY KEY (line_id) ); CREATE TABLE IF NOT EXISTS section ( - id BIGINT AUTO_INCREMENT NOT NULL, - departure_id BIGINT NOT NULL, - arrival_id BIGINT NOT NULL, + section_id BIGINT AUTO_INCREMENT NOT NULL, + up_station_id BIGINT NOT NULL, + down_station_id BIGINT NOT NULL, distance INT NOT NULL, line_id BIGINT NOT NULL, - PRIMARY KEY (id) + list_order INT NOT NULL, + PRIMARY KEY (section_id) ); diff --git a/src/test/java/subway/Fixture.java b/src/test/java/subway/Fixture.java index 26d2ed9e7..8912ed2f4 100644 --- a/src/test/java/subway/Fixture.java +++ b/src/test/java/subway/Fixture.java @@ -1,28 +1,30 @@ package subway; -import subway.domain.*; +import subway.domain.Distance; +import subway.domain.Line; +import subway.domain.Section; +import subway.domain.Sections; +import subway.domain.Station; -import java.util.HashMap; import java.util.List; -import java.util.Map; public class Fixture { - public static final Line line = new Line(1L, "2호선", "green"); public static final Station stationA = new Station(1L, "A"); public static final Station stationB = new Station(2L, "B"); public static final Station stationC = new Station(3L, "C"); - public static final Section sectionAB = new Section(2, stationA, stationB, line); - public static final Section sectionBA = new Section(2, stationB, stationA, line); - public static final Section sectionBC = new Section(1, stationB, stationC, line); - public static final Section sectionCB = new Section(1, stationC, stationB, line); - public static final Map tempSubwayMap = new HashMap() {{ - put(stationA, new Sections(List.of(sectionAB))); - put(stationB, new Sections(List.of(sectionBA, sectionBC))); - put(stationC, new Sections(List.of(sectionCB))); - }}; - public static final Map tempLineMap = new HashMap() {{ - put(line, stationA); - }}; - public static final SubwayMap subwayMap = new SubwayMap(tempSubwayMap, tempLineMap); + public static final Station stationD = new Station(4L, "D"); + public static final Station stationE = new Station(5L, "E"); + public static final Station stationF = new Station(6L, "F"); + public static final Distance distance1 = new Distance(1); + public static final Distance distance2 = new Distance(2); + public static final Section sectionAB = new Section(stationA, stationB, distance2); + public static final Section sectionBC = new Section(stationB, stationC, distance1); + public static final Section sectionCD = new Section(stationC, stationD, distance1); + public static final Section sectionDE = new Section(stationD, stationE, distance1); + public static final Section sectionAF = new Section(stationA, stationF, new Distance(2)); + public static final Section sectionFE = new Section(stationF, stationE, new Distance(3)); + public static final Line line1 = new Line(1L, "1호선", "blue", new Sections(List.of(sectionAB, sectionBC))); + public static final Line line2 = new Line(2L, "2호선", "green", new Sections()); + public static final Line line3 = new Line(3L, "3호선", "orange", new Sections()); } diff --git a/src/test/java/subway/application/LineServiceTest.java b/src/test/java/subway/application/LineServiceTest.java new file mode 100644 index 000000000..f4158bf43 --- /dev/null +++ b/src/test/java/subway/application/LineServiceTest.java @@ -0,0 +1,135 @@ +package subway.application; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import subway.Fixture; +import subway.domain.Line; +import subway.domain.LineRepository; +import subway.domain.Lines; +import subway.dto.request.LineRequest; +import subway.dto.response.LineResponse; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.refEq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = LineService.class) +class LineServiceTest { + + @MockBean + private LineRepository lineRepository; + @Autowired + private LineService lineService; + + @Test + @DisplayName("요청의 이름, 색깔에 해당하는 라인을 생성한다.") + void createLine() { + // given + final LineRequest request = new LineRequest("2호선", "green"); + final Line expectedLine = new Line(1L, request.getName(), request.getColor()); + when(lineRepository.save(any())).thenReturn(expectedLine); + + // when + final LineResponse response = lineService.createLine(request); + + // then + assertThat(response.getId()).isEqualTo(expectedLine.getId()); + + verify(lineRepository, times(1)).save(any()); + } + + @Test + @DisplayName("라인을 저장한다") + void save() { + // given + final Line line = new Line(1L, "잠실역", "green"); + + // when + lineService.save(line); + + // then + verify(lineRepository, times(1)).save(any()); + } + + @Test + @DisplayName("id에 해당하는 라인을 요청의 이름과 색깔로 변경한다") + void updateLine() { + // given + final LineRequest request = new LineRequest("선릉역", "green"); + final Line found = new Line(1L, "잠실역", "green"); + when(lineRepository.findById(found.getId())).thenReturn(Optional.of(found)); + + // when + lineService.updateLine(found.getId(), request); + + // then + final Line updated = new Line(found.getId(), request.getName(), request.getColor()); + verify(lineRepository, times(1)).save(refEq(updated, "sections")); + } + + @Test + @DisplayName("id에 해당하는 라인을 삭제한다") + void deleteLineById() { + // given + final Line line = new Line(1L, "잠실역", "green"); + when(lineRepository.findById(1L)).thenReturn(Optional.of(line)); + + // when + lineService.deleteLineById(1L); + + // then + verify(lineRepository, times(1)).delete(line); + } + + @Test + @DisplayName("모든 라인을 조회한다") + void findLineResponses() { + when(lineRepository.findAll()).thenReturn(new Lines(List.of(Fixture.line1, Fixture.line2))); + + // when + final List lineResponses = lineService.findLineResponses(); + + // then + assertThat(lineResponses.size()).isEqualTo(2); + + verify(lineRepository, times(1)).findAll(); + } + + @Test + @DisplayName("id에 해당하는 라인을 조회한다") + void findLineResponseById() { + // given + final Line line = new Line(1L, "잠실역", "green"); + when(lineRepository.findById(1L)).thenReturn(Optional.of(line)); + + // when + lineService.findLineResponseById(1L); + + verify(lineRepository, times(1)).findById(1L); + } + + @Test + @DisplayName("id에 해당하는 라인을 조회한다") + void findById() { + // given + final Line line = new Line(1L, "잠실역", "green"); + when(lineRepository.findById(1L)).thenReturn(Optional.of(line)); + + // when + lineService.findById(1L); + + verify(lineRepository, times(1)).findById(1L); + } +} \ No newline at end of file diff --git a/src/test/java/subway/application/StationServiceTest.java b/src/test/java/subway/application/StationServiceTest.java index 91839bd33..0a003f2dc 100644 --- a/src/test/java/subway/application/StationServiceTest.java +++ b/src/test/java/subway/application/StationServiceTest.java @@ -3,93 +3,106 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import subway.Fixture; -import subway.dao.SubwayMapRepository; -import subway.domain.Line; -import subway.domain.SubwayMap; -import subway.dto.StationRequest; - -import java.util.Collections; -import java.util.HashMap; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import subway.domain.Station; +import subway.dto.request.StationRequest; +import subway.persistance.StationDao; + +import java.util.Optional; + +import static org.mockito.ArgumentMatchers.refEq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = StationService.class) class StationServiceTest { - @Mock - private SubwayMapRepository subwayMapRepository; - @InjectMocks + @Autowired private StationService stationService; - - @Mock - private LineService lineService; + @MockBean + private StationDao stationDao; @Test - @DisplayName("CreateType이 INIT일 때 두 개의 역을 저장하고 저장한 역의 ID를 반환한다") - void saveStationINIT() { + @DisplayName("역을 저장한다") + void saveStation() { // given - final StationRequest stationRequestUp = new StationRequest("잠실역", "INIT", "2호선", true, false, null, "선릉역", 1, 1); - final StationRequest stationRequestDown = new StationRequest("선릉역", "INIT", "2호선", false, true, "잠실역", null, 1, 1); - final Line line = new Line(1L, "2호선", "green"); - + final StationRequest request = new StationRequest("잠실역"); + final Station station = new Station(request.getName()); + when(stationDao.insert(refEq(station))).thenReturn(new Station(1L, station.getName())); - final SubwayMap subwayMap = new SubwayMap(new HashMap<>(Collections.emptyMap()), new HashMap<>(Fixture.tempLineMap)); - when(subwayMapRepository.find()).thenReturn(subwayMap); - when(lineService.findLineByName(line.getName())).thenReturn(line); + // when + stationService.saveStation(request); - // when & then - assertDoesNotThrow(() -> stationService.saveStation(List.of(stationRequestUp, stationRequestDown))); - verify(subwayMapRepository, times(1)).save(any()); + // then + verify(stationDao, times(1)).insert(refEq(station)); } @Test - @DisplayName("CreateType이 UP일 때 두 개의 역을 저장하고 저장한 역의 ID를 반환한다") - void saveStationUP() { + @DisplayName("id에 해당하는 역을 수정한다") + void updateStation() { // given - final StationRequest stationRequestDown = new StationRequest("잠실역", "UP", "2호선", true, false, null, "A", null, 1); - final Line line = new Line(1L, "2호선", "green"); + final StationRequest request = new StationRequest("선릉역"); + final Station station = new Station(1L, "잠실역"); + when(stationDao.findById(1L)).thenReturn(Optional.of(station)); + + // when + stationService.updateStation(1L, request); - when(subwayMapRepository.find()).thenReturn(Fixture.subwayMap); - when(lineService.findLineByName(line.getName())).thenReturn(line); + // then + verify(stationDao, times(1)) + .update(new Station(station.getId(), request.getName())); + } + + @Test + @DisplayName("id에 해당하는 역을 삭제한다") + void deleteStationById() { + // when + stationService.deleteStationById(1L); - // when & then - assertDoesNotThrow(() -> stationService.saveStation(List.of(stationRequestDown))); - verify(subwayMapRepository, times(1)).save(any()); + // then + verify(stationDao, times(1)).deleteById(1L); } @Test - @DisplayName("CreateType이 DOWN일 때 두 개의 역을 저장하고 저장한 역의 ID를 반환한다") - void saveStationDown() { + @DisplayName("id에 해당하는 역을 조회한다") + void findStationResponseById() { // given - final StationRequest stationRequestDown = new StationRequest("잠실역", "DOWN", "2호선", false, true, "C", null, 1, null); - final Line line = new Line(1L, "2호선", "green"); + final Station station = new Station(1L, "잠실역"); + when(stationDao.findById(1L)).thenReturn(Optional.of(station)); - when(subwayMapRepository.find()).thenReturn(Fixture.subwayMap); - when(lineService.findLineByName(line.getName())).thenReturn(line); + // when + stationService.findStationResponseById(1L); - // when & then - assertDoesNotThrow(() -> stationService.saveStation(List.of(stationRequestDown))); - verify(subwayMapRepository, times(1)).save(any()); + // then + verify(stationDao, times(1)).findById(1L); } @Test - @DisplayName("CreateType이 MID일 때 두 개의 역을 저장하고 저장한 역의 ID를 반환한다") - void saveStationMid() { + @DisplayName("모든 역을 조회한다") + void findAllStationResponses() { + // when + stationService.findAllStationResponses(); + + // then + verify(stationDao, times(1)).findAll(); + } + + @Test + @DisplayName("id에 해당하는 역을 조회한다") + void findById() { // given - final StationRequest request = new StationRequest("잠실역", "MID", "2호선", false, false, "A", "B", 1, 1); - final Line line = new Line(1L, "2호선", "green"); - when(subwayMapRepository.find()).thenReturn(Fixture.subwayMap); - when(lineService.findLineByName(line.getName())).thenReturn(line); - - // when & then - assertDoesNotThrow(() -> stationService.saveStation(List.of(request))); - verify(subwayMapRepository, times(1)).save(any()); + final Station station = new Station(1L, "잠실역"); + when(stationDao.findById(1L)).thenReturn(Optional.of(station)); + + // when + stationService.findById(1L); + + // then + verify(stationDao, times(1)).findById(1L); } -} +} \ No newline at end of file diff --git a/src/test/java/subway/dao/LineDaoTest.java b/src/test/java/subway/dao/LineDaoTest.java deleted file mode 100644 index 61bc6ebdf..000000000 --- a/src/test/java/subway/dao/LineDaoTest.java +++ /dev/null @@ -1,56 +0,0 @@ -package subway.dao; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.test.context.jdbc.Sql; -import subway.dao.entity.LineEntity; -import subway.domain.Line; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; - -@JdbcTest -@Sql("classpath:schema.sql") -class LineDaoTest { - - private final LineDao lineDao; - - private LineDaoTest(@Autowired final JdbcTemplate jdbcTemplate) { - this.lineDao = new LineDao(jdbcTemplate); - } - - @Test - @DisplayName("이름으로 Line을 조회할 수 있다") - void findByName() { - // given - final Line line = new Line("2호선", "green"); - lineDao.insert(new LineEntity(line.getName(), line.getColor())); - - // when - final Line foundLine = lineDao.findByName(line.getName()); - - // then - assertThat(foundLine.getName()).isEqualTo(line.getName()); - } - - @Test - void update() { - // given - final Line line = new Line("2호선", "green"); - final Line insertedLine = lineDao.insert(new LineEntity(line.getName(), line.getColor())); - final Line newLine = new Line("1호선", "blue"); - - // when - lineDao.update(insertedLine.getId(), newLine); - - // then - final Line foundLine = lineDao.findById(insertedLine.getId()); - assertAll( - () -> assertThat(foundLine.getName()).isEqualTo(newLine.getName()), - () -> assertThat(foundLine.getColor()).isEqualTo(newLine.getColor()) - ); - } -} diff --git a/src/test/java/subway/dao/SectionDaoTest.java b/src/test/java/subway/dao/SectionDaoTest.java deleted file mode 100644 index f7bcb8318..000000000 --- a/src/test/java/subway/dao/SectionDaoTest.java +++ /dev/null @@ -1,86 +0,0 @@ -package subway.dao; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.test.context.jdbc.Sql; -import subway.dao.entity.SectionEntity; -import subway.domain.Section; - -import java.util.List; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; -import static subway.Fixture.*; - -@JdbcTest -@Sql("classpath:schema.sql") -class SectionDaoTest { - - private final SectionDao sectionDao; - - private SectionDaoTest(@Autowired final JdbcTemplate jdbcTemplate) { - this.sectionDao = new SectionDao(jdbcTemplate); - } - - @Test - @DisplayName("Section 입력 테스트") - void insertSection() { - // given - // when - sectionDao.insertSection(sectionAB); - - // then - final List sectionEntities = sectionDao.findByLineId(line.getId()); - assertThat(sectionEntities.size()).isEqualTo(1); - } - - @Test - @DisplayName("LineId로 section을 조회한다.") - void findByLineId() { - // given - sectionDao.insertSection(sectionAB); - - // when - final List sectionEntities = sectionDao.findByLineId(line.getId()); - - // then - assertThat(sectionEntities.get(0).getLineId()).isEqualTo(line.getId()); - } - - @Test - @DisplayName("StationId로 Section을 조회한다") - void findByStationIds() { - // given - sectionDao.insertSection(sectionAB); - - // when - final SectionEntity sectionEntity = sectionDao.findByStationIds(stationA.getId(), stationB.getId()); - - // then - assertAll( - () -> assertThat(sectionEntity.getDepartureId()).isEqualTo(stationA.getId()), - () -> assertThat(sectionEntity.getArrivalId()).isEqualTo(stationB.getId()) - ); - } - - @Test - @DisplayName("Section의 정보를 수정한다") - void update() { - // given - sectionDao.insertSection(sectionAB); - - // when - final Section updateSection = new Section(2, stationA, stationC, line); - sectionDao.update(sectionAB, updateSection); - - // then - final SectionEntity entity = sectionDao.findByLineId(line.getId()).get(0); - assertAll( - () -> assertThat(entity.getDistance()).isEqualTo(updateSection.getDistance()), - () -> assertThat(entity.getArrivalId()).isEqualTo(updateSection.getArrival().getId()) - ); - } -} diff --git a/src/test/java/subway/dao/StationDaoTest.java b/src/test/java/subway/dao/StationDaoTest.java deleted file mode 100644 index 9ea5a016f..000000000 --- a/src/test/java/subway/dao/StationDaoTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package subway.dao; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.test.context.jdbc.Sql; -import subway.domain.Station; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -@JdbcTest -@Sql("classpath:schema.sql") -class StationDaoTest { - - private final StationDao stationDao; - - private StationDaoTest(@Autowired final JdbcTemplate jdbcTemplate) { - this.stationDao = new StationDao(jdbcTemplate); - } - - @Test - @DisplayName("이름으로 Station 을 조회한다") - void findByName() { - // given - final Station station = new Station("잠실역"); - stationDao.insert(station); - - // when - final Station foundStation = stationDao.findByName(station.getName()); - - // then - assertThat(foundStation.getName()).isEqualTo(station.getName()); - } -} diff --git a/src/test/java/subway/dao/SubwayMapRepositoryTest.java b/src/test/java/subway/dao/SubwayMapRepositoryTest.java deleted file mode 100644 index 6aa5f75f8..000000000 --- a/src/test/java/subway/dao/SubwayMapRepositoryTest.java +++ /dev/null @@ -1,62 +0,0 @@ -package subway.dao; - -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.jdbc.Sql; -import subway.domain.Section; -import subway.domain.Station; -import subway.domain.SubwayMap; - -import java.util.HashMap; -import java.util.List; -import java.util.Set; - -import static org.junit.jupiter.api.Assertions.assertAll; -import static subway.Fixture.*; - - -@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) -@JdbcTest -@Sql("classpath:schema.sql") -class SubwayMapRepositoryTest { - - private final StationDao stationDao; - private final SectionDao sectionDao; - private final LineDao lineDao; - private final SubwayMapRepository subwayMapRepository; - - private SubwayMapRepositoryTest(@Autowired final JdbcTemplate jdbcTemplate) { - this.stationDao = new StationDao(jdbcTemplate); - this.sectionDao = new SectionDao(jdbcTemplate); - this.lineDao = new LineDao(jdbcTemplate); - this.subwayMapRepository = new SubwayMapRepository(stationDao, sectionDao, lineDao); - } - - @Test - @DisplayName("SubwayMap을 생성 및 조회 한다") - void find() { - // given - final SubwayMap subwayMap1 = new SubwayMap(new HashMap<>(subwayMap.getSubwayMap()), new HashMap<>(subwayMap.getEndpointMap())); - subwayMapRepository.save(subwayMap1); - - // when - final SubwayMap subwayMap = subwayMapRepository.find(); - - //then - final Set stations = subwayMap.getSubwayMap().keySet(); - final List
aSections = subwayMap.getSubwayMap().get(stationA).getSections(); - final List
bSections = subwayMap.getSubwayMap().get(stationB).getSections(); - final List
cSections = subwayMap.getSubwayMap().get(stationC).getSections(); - assertAll( - () -> Assertions.assertThat(stations).containsOnly(stationA, stationB, stationC), - () -> Assertions.assertThat(aSections).containsOnly(sectionAB), - () -> Assertions.assertThat(bSections).containsOnly(sectionBA, sectionBC), - () -> Assertions.assertThat(cSections).containsOnly(sectionCB) - ); - } -} diff --git a/src/test/java/subway/domain/DistanceTest.java b/src/test/java/subway/domain/DistanceTest.java new file mode 100644 index 000000000..c22912956 --- /dev/null +++ b/src/test/java/subway/domain/DistanceTest.java @@ -0,0 +1,47 @@ +package subway.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import subway.exceptions.IllegalDistanceException; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +class DistanceTest { + + @ParameterizedTest(name = "거리가 {0}(양의 정수가 아니면) 예외가 발생한다") + @ValueSource(ints = {0, -1}) + void init_throw(final int value) { + assertThatThrownBy(() -> new Distance(value)) + .isInstanceOf(IllegalDistanceException.class); + } + + @ParameterizedTest(name = "거리가 {0}(양의 정수이면) 예외가 발생하지 않는다") + @ValueSource(ints = {1, 2, 3, 100000000}) + void init(final int value) { + assertThatCode(() -> new Distance(value)) + .doesNotThrowAnyException(); + } + + @Test + @DisplayName("10 - 6은 4이다.") + void sub() { + final Distance ten = new Distance(10); + final Distance six = new Distance(6); + + assertThat(ten.sub(six).getValue()).isEqualTo(4); + } + + @Test + @DisplayName("결과가 음수이면 예외를 발생한다.") + void sub_throw() { + final Distance six = new Distance(6); + final Distance ten = new Distance(10); + + assertThatThrownBy(() -> six.sub(ten)) + .isInstanceOf(IllegalDistanceException.class); + } +} \ No newline at end of file diff --git a/src/test/java/subway/domain/SectionTest.java b/src/test/java/subway/domain/SectionTest.java new file mode 100644 index 000000000..28f979452 --- /dev/null +++ b/src/test/java/subway/domain/SectionTest.java @@ -0,0 +1,53 @@ +package subway.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import subway.Fixture; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +class SectionTest { + + @Test + @DisplayName("역A를 구간BC의 상행 역인 B에 연결한다") + void connectToUp() { + // given & when + final Section section = Fixture.sectionBC.connectToUp(Fixture.stationA, Fixture.distance1); + + // then + assertAll( + () -> assertThat(section.getUp()).isEqualTo(Fixture.stationA), + () -> assertThat(section.getDown()).isEqualTo(Fixture.stationB), + () -> assertThat(section.getDistance()).isEqualTo(Fixture.distance1) + ); + } + + @Test + @DisplayName("역C를 구간AB의 하행 역인 C에 연결한다") + void connectToDown() { + // given & when + final Section section = Fixture.sectionAB.connectToDown(Fixture.stationC, Fixture.distance1); + + // then + assertAll( + () -> assertThat(section.getUp()).isEqualTo(Fixture.stationB), + () -> assertThat(section.getDown()).isEqualTo(Fixture.stationC), + () -> assertThat(section.getDistance()).isEqualTo(Fixture.distance1) + ); + } + + @Test + @DisplayName("역C를 구간AB의 사이에 연결한다") + void connectIntermediate() { + // given && when + final Section section = Fixture.sectionAB.connectIntermediate(Fixture.stationC, Fixture.distance1); + + // then + assertAll( + () -> assertThat(section.getUp()).isEqualTo(Fixture.stationA), + () -> assertThat(section.getDown()).isEqualTo(Fixture.stationC), + () -> assertThat(section.getDistance()).isEqualTo(Fixture.distance1) + ); + } +} \ No newline at end of file diff --git a/src/test/java/subway/domain/SectionsTest.java b/src/test/java/subway/domain/SectionsTest.java new file mode 100644 index 000000000..2e38fa945 --- /dev/null +++ b/src/test/java/subway/domain/SectionsTest.java @@ -0,0 +1,154 @@ +package subway.domain; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import subway.Fixture; + +import java.util.List; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +class SectionsTest { + @DisplayName("역 A와 B를 초기 역등록한다.") + @Test + void addInitStations() { + // given + final Sections sections = new Sections(); + + // when + sections.addInitStations(Fixture.stationA, Fixture.stationB, Fixture.distance2); + + // then + final Section section = sections.getSections().get(0); + assertThat(section.getUp()).isEqualTo(Fixture.stationA); + assertThat(section.getDown()).isEqualTo(Fixture.stationB); + } + + @DisplayName("구간 BC에 상행 종점 A를 추가하면 구간 AB가 생긴다") + @Test + void addUpEndpoint() { + // given + final Sections sections = new Sections(List.of(Fixture.sectionBC)); + + // when + sections.addUpEndpoint(Fixture.stationA, Fixture.distance1); + + // then + final Section section = sections.getSections().get(0); + assertThat(section.getUp()).isEqualTo(Fixture.stationA); + assertThat(section.getDown()).isEqualTo(Fixture.stationB); + } + + @DisplayName("구간 AB에 하행 종점 C를 추가하면 구간 BC가 생긴다") + @Test + void addDownEndpoint() { + // given + final Sections sections = new Sections(List.of(Fixture.sectionAB)); + + // when + sections.addDownEndpoint(Fixture.stationC, Fixture.distance1); + + // then + final Section section = sections.getSections().get(1); + assertThat(section.getUp()).isEqualTo(Fixture.stationB); + assertThat(section.getDown()).isEqualTo(Fixture.stationC); + } + + @DisplayName("구간 AB 중간에 C를 추가하면 구간 AB, BC가 생긴다") + @Test + void addIntermediate() { + // given + final Sections sections = new Sections(List.of(Fixture.sectionAB)); + + // when + sections.addIntermediate(Fixture.stationC, Fixture.stationA, Fixture.distance1); + + // then + final Section prevToThis = sections.getSections().get(0); + final Section thisToNext = sections.getSections().get(1); + + assertThat(prevToThis.getUp()).isEqualTo(Fixture.stationA); + assertThat(prevToThis.getDown()).isEqualTo(Fixture.stationC); + assertThat(thisToNext.getUp()).isEqualTo(Fixture.stationC); + assertThat(thisToNext.getDown()).isEqualTo(Fixture.stationB); + } + + @DisplayName("구간 중간의 역을 삭제하면 앞의 역과 다음 역이 이어지고 거리는 두 구간 거리의 합이다") + @Test + void deleteMid() { + final Sections sections = new Sections(List.of(Fixture.sectionAB, Fixture.sectionBC)); + + // when + sections.delete(Fixture.stationB); + + //then + Assertions.assertThat(sections.getSections()).contains( + new Section(Fixture.stationA, + Fixture.stationC, + Fixture.sectionAB.getDistance().sum(Fixture.sectionBC.getDistance()) + ) + ); + } + + @DisplayName("역이 두 개이면 모든 구간을 삭제한다") + @Test + void deleteInit() { + final Sections sections = new Sections(List.of(Fixture.sectionAB)); + + // when + sections.delete(Fixture.stationA); + + //then + assertThat(sections.getSections().size()).isEqualTo(0); + } + + @DisplayName("상행 종점을 삭제한다") + @Test + void deleteUp() { + final Sections sections = new Sections(List.of(Fixture.sectionAB, Fixture.sectionBC)); + + // when + sections.delete(Fixture.stationA); + + //then + Assertions.assertThat(sections.getSections()).containsOnly(Fixture.sectionBC); + } + + @DisplayName("하행 종점을 삭제한다") + @Test + void deleteDown() { + final Sections sections = new Sections(List.of(Fixture.sectionAB, Fixture.sectionBC)); + + // when + sections.delete(Fixture.stationC); + + //then + Assertions.assertThat(sections.getSections()).containsOnly(Fixture.sectionAB); + } + + + @DisplayName("모든 역을 조회한다") + @Test + void getAllStations() { + final Sections sections = new Sections(List.of(Fixture.sectionAB, Fixture.sectionBC)); + + // when + final List stations = sections.getAllStations(); + + //then + Assertions.assertThat(stations).containsOnly(Fixture.stationA, Fixture.stationB, Fixture.stationC); + } + + @DisplayName("모든 구간의 거리를 조회한다") + @Test + void getAllDistances() { + final Sections sections = new Sections(List.of(Fixture.sectionAB, Fixture.sectionBC)); + + // when + final List distances = sections.getAllDistances(); + + //then + Assertions.assertThat(distances).containsOnly(Fixture.sectionAB.getDistance(), Fixture.sectionBC.getDistance()); + } +} \ No newline at end of file diff --git a/src/test/java/subway/domain/SubwayMapTest.java b/src/test/java/subway/domain/SubwayMapTest.java deleted file mode 100644 index be2a4a699..000000000 --- a/src/test/java/subway/domain/SubwayMapTest.java +++ /dev/null @@ -1,224 +0,0 @@ -package subway.domain; - -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; -import static subway.Fixture.*; - -class SubwayMapTest { - - @Test - @DisplayName("A, B 두 역 사이에 D역을 추가한다") - void addIntermediateStation() { - // given - final SubwayMap subwayMap1 = new SubwayMap(new HashMap<>(subwayMap.getSubwayMap()), new HashMap<>(subwayMap.getEndpointMap())); - - final Station thisStation = new Station("D"); - final Section thisToA = new Section(1, thisStation, stationA, line); - final Section thisToB = new Section(1, thisStation, stationB, line); - - // when - subwayMap1.addIntermediateStation(thisToA, thisToB); - - // then - final List
sectionByStation = subwayMap1.findSectionByStation(stationA).getSections(); - Assertions.assertThat(sectionByStation).contains(thisToA.getReverse()); - Assertions.assertThat(sectionByStation).doesNotContain(sectionAB); - } - - @Test - @DisplayName("삽입하려는 D역과 A역의 거리와 D역과 B역의 거리의 합이 A역과 B역의 거리의 합과 다르면 예외가 발생한다") - void addIntermediateStation_throws() { - // given - final SubwayMap subwayMap1 = new SubwayMap(new HashMap<>(subwayMap.getSubwayMap()), new HashMap<>(subwayMap.getEndpointMap())); - - final Station thisStation = new Station("D"); - final Section thisToA = new Section(2, thisStation, stationA, line); - final Section thisToB = new Section(1, thisStation, stationB, line); - - // when & then - assertThatThrownBy(() -> subwayMap1.addIntermediateStation(thisToA, thisToB)) - .isInstanceOf(IllegalArgumentException.class); - } - - @Test - @DisplayName("초기 라인에 두 개의 역을 추가한다.") - void addInitialStations() { - // given - final Map map = new HashMap<>(); - final Map lineMap = new HashMap<>(); - final SubwayMap subwayMap1 = new SubwayMap(map, lineMap); - - // when - subwayMap1.addInitialStations(sectionAB, sectionBA); - - // then - final List
sectionsFromA = subwayMap1.findSectionByStation(stationA).getSections(); - final List
sectionsFromB = subwayMap1.findSectionByStation(stationB).getSections(); - - Assertions.assertThat(sectionsFromA).containsOnly(sectionAB); - Assertions.assertThat(sectionsFromB).containsOnly(sectionBA); - } - - @Test - @DisplayName("라인이 초기 상태가 아닐 때 초기 라인에 두 개의 역을 추가하면 예외가 발생한다.") - void addInitialStations_not_init_throw() { - // given - final SubwayMap subwayMap1 = new SubwayMap(new HashMap<>(subwayMap.getSubwayMap()), new HashMap<>(subwayMap.getEndpointMap())); - - // when - assertThatThrownBy(() -> subwayMap1.addInitialStations(sectionAB, sectionBA)) - .isInstanceOf(IllegalArgumentException.class); - } - - @Test - @DisplayName("상행 종점 역Z를 추가하면 기존 상행 종점역 A는 SectionAZ를, Z는 오직 SectionZA만 갖는다.") - void addUpEndPointStation() { - final SubwayMap subwayMap1 = new SubwayMap(new HashMap<>(subwayMap.getSubwayMap()), new HashMap<>(subwayMap.getEndpointMap())); - // given - final Station stationZ = new Station("Z"); - final Section sectionZA = new Section(1, stationZ, stationA, line); - - // when - subwayMap1.addUpEndPoint(sectionZA); - - // then - final List
sectionsFromA = subwayMap1.findSectionByStation(stationA).getSections(); - final List
sectionsFromZ = subwayMap1.findSectionByStation(stationZ).getSections(); - Assertions.assertThat(sectionsFromA).contains(sectionZA.getReverse()); - Assertions.assertThat(sectionsFromZ).containsOnly(sectionZA); - } - - @Test - @DisplayName("하행 종점 역Z를 추가하면 기존 하행 종점역 C는 SectionCZ를, Z는 오직 SectionZC만 갖는다.") - void addDownEndPointStation() { - // given - final SubwayMap subwayMap1 = new SubwayMap(new HashMap<>(subwayMap.getSubwayMap()), new HashMap<>(subwayMap.getEndpointMap())); - - final Station stationZ = new Station("Z"); - final Section sectionZC = new Section(1, stationZ, stationC, line); - - // when - subwayMap1.addDownEndPoint(sectionZC); - - // then - final List
sectionsFromC = subwayMap1.findSectionByStation(stationC).getSections(); - final List
sectionsFromZ = subwayMap1.findSectionByStation(stationZ).getSections(); - Assertions.assertThat(sectionsFromC).contains(sectionZC.getReverse()); - Assertions.assertThat(sectionsFromZ).containsOnly(sectionZC); - } - - @Test - @DisplayName("종점 추가시 선택한 역이 종점이 아닐 경우 예외가 발생한다") - void addEndPointStation_notEntPoint_throw() { - // given - final SubwayMap subwayMap1 = new SubwayMap(new HashMap<>(subwayMap.getSubwayMap()), new HashMap<>(subwayMap.getEndpointMap())); - - final Station stationZ = new Station("Z"); - final Section sectionZB = new Section(1, stationZ, stationB, line); - - // when & then - assertThatThrownBy(() -> subwayMap1.addDownEndPoint(sectionZB)) - .isInstanceOf(IllegalArgumentException.class); - } - - @Test - @DisplayName("연결되지 않은 A, C 두 역 사이에 D역을 추가하면 예외가 발생한다") - void addIntermediateStation_not_connected_throw() { - // given - final SubwayMap subwayMap1 = new SubwayMap(new HashMap<>(subwayMap.getSubwayMap()), new HashMap<>(subwayMap.getEndpointMap())); - - final Station thisStation = new Station("D"); - final Section thisToA = new Section(1, thisStation, stationA, line); - final Section thisToC = new Section(1, thisStation, stationC, line); - - // when - assertThatThrownBy(() -> subwayMap1.addIntermediateStation(thisToA, thisToC)) - .isInstanceOf(IllegalArgumentException.class); - } - - @Test - @DisplayName("상행 종점을 삭제") - void removeUpEndpoint() { - // given - final SubwayMap subwayMap1 = new SubwayMap(new HashMap<>(subwayMap.getSubwayMap()), new HashMap<>(subwayMap.getEndpointMap())); - - // when - subwayMap1.deleteStation(stationA.getId()); - - // then - final List
sectionList = subwayMap1.getSubwayMap().values().stream() - .flatMap(sections -> sections.getSections().stream()) - .collect(Collectors.toList()); - Assertions.assertThat(sectionList).containsExactly(sectionBC, sectionCB); - } - - @Test - @DisplayName("하행 종점을 삭제") - void removeDownEndpoint() { - // given - final SubwayMap subwayMap1 = new SubwayMap(new HashMap<>(subwayMap.getSubwayMap()), new HashMap<>(subwayMap.getEndpointMap())); - - // when - subwayMap1.deleteStation(stationC.getId()); - - // then - final List
sectionList = subwayMap1.getSubwayMap().values().stream() - .flatMap(sections -> sections.getSections().stream()) - .collect(Collectors.toList()); - Assertions.assertThat(sectionList).containsExactly(sectionAB, sectionBA); - } - - @Test - @DisplayName("마지막 두 역 삭제") - void removeLastStations() { - // given - final SubwayMap subwayMap1 = new SubwayMap(new HashMap<>(subwayMap.getSubwayMap()), new HashMap<>(subwayMap.getEndpointMap())); - subwayMap1.deleteStation(stationC.getId()); - - // when - subwayMap1.deleteStation(stationA.getId()); - - // then - final List
sectionList = subwayMap1.getSubwayMap().values().stream() - .flatMap(sections -> sections.getSections().stream()) - .collect(Collectors.toList()); - Assertions.assertThat(sectionList.isEmpty()).isTrue(); - } - - @Test - @DisplayName("중간 역을 삭제") - void removeMidStation() { - // given - final SubwayMap subwayMap1 = new SubwayMap(new HashMap<>(subwayMap.getSubwayMap()), new HashMap<>(subwayMap.getEndpointMap())); - final Section sectionAC = new Section(3, stationA, stationC, line); - final Section sectionCA = new Section(3, stationC, stationA, line); - - // when - subwayMap1.deleteStation(stationB.getId()); - - // then - final List
sectionList = subwayMap1.getSubwayMap().values().stream() - .flatMap(sections -> sections.getSections().stream()) - .collect(Collectors.toList()); - sectionList.forEach(section -> System.out.println(section.getDeparture().getName() + section.getArrival().getName())); - Assertions.assertThat(sectionList).containsExactly(sectionAC, sectionCA); - } - - @Test - void test() { - // given - final SubwayMap subwayMap1 = new SubwayMap(new HashMap<>(subwayMap.getSubwayMap()), new HashMap<>(subwayMap.getEndpointMap())); - - final List stations = subwayMap1.stationsInLine(line); - - stations.forEach(station -> System.out.println(station.getName())); - } -} diff --git a/src/test/java/subway/domain/fare/FareCalculatorTest.java b/src/test/java/subway/domain/fare/FareCalculatorTest.java new file mode 100644 index 000000000..4624f7876 --- /dev/null +++ b/src/test/java/subway/domain/fare/FareCalculatorTest.java @@ -0,0 +1,24 @@ +package subway.domain.fare; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import subway.domain.fare.distance.BasicFarePolicy; +import subway.domain.fare.distance.EightUnitFarePolicy; +import subway.domain.fare.distance.FiveUnitFarePolicy; + +import java.util.List; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +class FareCalculatorTest { + + @ParameterizedTest(name = "거리가 {0}km 이면 요금은 {1}원이다.") + @CsvSource(value = {"9:1250", "10:1250", "11:1350", "16:1450", + "49:2050", "50:2050", "51:2150", "57:2150", "58:2150", "59:2250"}, delimiter = ':') + void test(final int distance, final int expectedFare) { + final FareCalculator fareCalculator = new FareCalculator(new DiscountChain(List.of(new BasicFarePolicy(), new EightUnitFarePolicy(), new FiveUnitFarePolicy()))); + final int fare = fareCalculator.calculate(distance); + + assertThat(fare).isEqualTo(expectedFare); + } +} \ No newline at end of file diff --git a/src/test/java/subway/domain/graph/SubwayGraphTest.java b/src/test/java/subway/domain/graph/SubwayGraphTest.java new file mode 100644 index 000000000..8e9ee0399 --- /dev/null +++ b/src/test/java/subway/domain/graph/SubwayGraphTest.java @@ -0,0 +1,36 @@ +package subway.domain.graph; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import subway.Fixture; +import subway.domain.Line; +import subway.domain.Lines; +import subway.domain.Section; +import subway.domain.Sections; +import subway.domain.Station; + +import java.util.List; + +class SubwayGraphTest { + + @Test + void findPath() { + + final List
sections = List.of( + Fixture.sectionAB, + Fixture.sectionBC, + Fixture.sectionCD, + Fixture.sectionDE, + Fixture.sectionAF, + Fixture.sectionFE + ); + + final Line line = new Line(1L, "잠실역", "green", new Sections(sections)); + final Lines lines = new Lines(List.of(line)); + final SubwayGraph subwayGraph = SubwayGraph.from(lines); + + final List shortestPath = subwayGraph.findPath(Fixture.stationA, Fixture.stationE); + + Assertions.assertThat(shortestPath).containsExactly(Fixture.stationA, Fixture.stationF, Fixture.stationE); + } +} \ No newline at end of file diff --git a/src/test/java/subway/integration/IntegrationTest.java b/src/test/java/subway/integration/IntegrationTest.java index c30949402..f68063058 100644 --- a/src/test/java/subway/integration/IntegrationTest.java +++ b/src/test/java/subway/integration/IntegrationTest.java @@ -3,7 +3,7 @@ import io.restassured.RestAssured; import org.junit.jupiter.api.BeforeEach; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.test.annotation.DirtiesContext; @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) diff --git a/src/test/java/subway/integration/LineIntegrationTest.java b/src/test/java/subway/integration/LineIntegrationTest.java index ad4170205..83f32c51a 100644 --- a/src/test/java/subway/integration/LineIntegrationTest.java +++ b/src/test/java/subway/integration/LineIntegrationTest.java @@ -8,8 +8,8 @@ import org.junit.jupiter.api.Test; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -import subway.dto.LineRequest; -import subway.dto.LineResponse; +import subway.dto.request.LineRequest; +import subway.dto.response.LineResponse; import java.util.List; import java.util.stream.Collectors; @@ -34,7 +34,7 @@ public void setUp() { @Test void createLine() { // when - ExtractableResponse response = RestAssured + final ExtractableResponse response = RestAssured .given().log().all() .contentType(MediaType.APPLICATION_JSON_VALUE) .body(lineRequest1) @@ -60,7 +60,7 @@ void createLineWithDuplicateName() { extract(); // when - ExtractableResponse response = RestAssured + final ExtractableResponse response = RestAssured .given().log().all() .contentType(MediaType.APPLICATION_JSON_VALUE) .body(lineRequest1) @@ -76,7 +76,7 @@ void createLineWithDuplicateName() { @Test void getLines() { // given - ExtractableResponse createResponse1 = RestAssured + final ExtractableResponse createResponse1 = RestAssured .given().log().all() .contentType(MediaType.APPLICATION_JSON_VALUE) .body(lineRequest1) @@ -84,7 +84,7 @@ void getLines() { .then().log().all(). extract(); - ExtractableResponse createResponse2 = RestAssured + final ExtractableResponse createResponse2 = RestAssured .given().log().all() .contentType(MediaType.APPLICATION_JSON_VALUE) .body(lineRequest2) @@ -93,7 +93,7 @@ void getLines() { extract(); // when - ExtractableResponse response = RestAssured + final ExtractableResponse response = RestAssured .given().log().all() .accept(MediaType.APPLICATION_JSON_VALUE) .when().get("/lines") @@ -102,10 +102,10 @@ void getLines() { // then assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()); - List expectedLineIds = Stream.of(createResponse1, createResponse2) + final List expectedLineIds = Stream.of(createResponse1, createResponse2) .map(it -> Long.parseLong(it.header("Location").split("/")[2])) .collect(Collectors.toList()); - List resultLineIds = response.jsonPath().getList(".", LineResponse.class).stream() + final List resultLineIds = response.jsonPath().getList(".", LineResponse.class).stream() .map(LineResponse::getId) .collect(Collectors.toList()); assertThat(resultLineIds).containsAll(expectedLineIds); @@ -115,7 +115,7 @@ void getLines() { @Test void getLine() { // given - ExtractableResponse createResponse = RestAssured + final ExtractableResponse createResponse = RestAssured .given().log().all() .contentType(MediaType.APPLICATION_JSON_VALUE) .body(lineRequest1) @@ -124,8 +124,8 @@ void getLine() { extract(); // when - Long lineId = Long.parseLong(createResponse.header("Location").split("/")[2]); - ExtractableResponse response = RestAssured + final Long lineId = Long.parseLong(createResponse.header("Location").split("/")[2]); + final ExtractableResponse response = RestAssured .given().log().all() .accept(MediaType.APPLICATION_JSON_VALUE) .when().get("/lines/{lineId}", lineId) @@ -134,7 +134,7 @@ void getLine() { // then assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()); - LineResponse resultResponse = response.as(LineResponse.class); + final LineResponse resultResponse = response.as(LineResponse.class); assertThat(resultResponse.getId()).isEqualTo(lineId); } @@ -142,7 +142,7 @@ void getLine() { @Test void updateLine() { // given - ExtractableResponse createResponse = RestAssured + final ExtractableResponse createResponse = RestAssured .given().log().all() .contentType(MediaType.APPLICATION_JSON_VALUE) .body(lineRequest1) @@ -151,8 +151,8 @@ void updateLine() { extract(); // when - Long lineId = Long.parseLong(createResponse.header("Location").split("/")[2]); - ExtractableResponse response = RestAssured + final Long lineId = Long.parseLong(createResponse.header("Location").split("/")[2]); + final ExtractableResponse response = RestAssured .given().log().all() .contentType(MediaType.APPLICATION_JSON_VALUE) .body(lineRequest2) @@ -168,7 +168,7 @@ void updateLine() { @Test void deleteLine() { // given - ExtractableResponse createResponse = RestAssured + final ExtractableResponse createResponse = RestAssured .given().log().all() .contentType(MediaType.APPLICATION_JSON_VALUE) .body(lineRequest1) @@ -177,8 +177,8 @@ void deleteLine() { extract(); // when - Long lineId = Long.parseLong(createResponse.header("Location").split("/")[2]); - ExtractableResponse response = RestAssured + final Long lineId = Long.parseLong(createResponse.header("Location").split("/")[2]); + final ExtractableResponse response = RestAssured .given().log().all() .when().delete("/lines/{lineId}", lineId) .then().log().all() diff --git a/src/test/java/subway/integration/LineStationIntegrationTest.java b/src/test/java/subway/integration/LineStationIntegrationTest.java new file mode 100644 index 000000000..07f7fc615 --- /dev/null +++ b/src/test/java/subway/integration/LineStationIntegrationTest.java @@ -0,0 +1,245 @@ +package subway.integration; + +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.context.jdbc.Sql; +import subway.dto.request.ConnectionEndpointRequest; +import subway.dto.request.ConnectionInitRequest; +import subway.dto.request.ConnectionMidRequest; +import subway.dto.response.LineStationResponse; +import subway.dto.response.StationResponse; +import subway.ui.EndpointType; + +import java.util.List; +import java.util.stream.Collectors; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +@Sql("/dummy.sql") +public class LineStationIntegrationTest extends IntegrationTest { + // 신분당선 + // 구신분당선 + // 1: 강남역 + // 2: 잠실역 + // 3: 건대입구역 + // 4: 선릉역 + // 5: 구의역 + + @BeforeEach + public void setUp() { + super.setUp(); + } + + @Test + @DisplayName("초기 두 역을 등록한다") + void init() { + // when + final ConnectionInitRequest request = new ConnectionInitRequest(2L, 10); + + final ExtractableResponse response = RestAssured.given().log().all() + .body(request) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when() + .patch("/lines/1/stations/1/init") + .then().log().all() + .extract(); + + // then + assertThat(response.statusCode()).isEqualTo(HttpStatus.NO_CONTENT.value()); + } + + @Test + @DisplayName("상행 종점에 역을 연결한다") + void up() { + // given + final ConnectionInitRequest initRequest = new ConnectionInitRequest(2L, 10); + + RestAssured.given() + .body(initRequest) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when() + .patch("/lines/1/stations/1/init"); + + // when + final ConnectionEndpointRequest request = new ConnectionEndpointRequest(EndpointType.UP.getValue(), 12); + + final ExtractableResponse response = RestAssured.given().log().all() + .body(request) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when() + .patch("/lines/1/stations/3/endpoint") + .then().log().all() + .extract(); + + // then + assertThat(response.statusCode()).isEqualTo(HttpStatus.NO_CONTENT.value()); + } + + @Test + @DisplayName("하행 종점에 역을 연결한다") + void down() { + // given + final ConnectionInitRequest initRequest = new ConnectionInitRequest(2L, 10); + + RestAssured.given() + .body(initRequest) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when() + .patch("/lines/1/stations/1/init"); + + // when & then + final ConnectionEndpointRequest request = new ConnectionEndpointRequest(EndpointType.DOWN.getValue(), 21); + + final ExtractableResponse response = RestAssured.given().log().all() + .body(request) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when() + .patch("/lines/1/stations/4/endpoint") + .then().log().all() + .extract(); + + // then + assertThat(response.statusCode()).isEqualTo(HttpStatus.NO_CONTENT.value()); + } + + @Test + @DisplayName("연결되어있는 역 중간에 역을 등록한다") + void mid() { + // given + final ConnectionInitRequest initRequest = new ConnectionInitRequest(2L, 10); + + RestAssured.given() + .body(initRequest) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when() + .patch("/lines/1/stations/1/init"); + + // when + final ConnectionMidRequest request = new ConnectionMidRequest(1L, 6); + + final ExtractableResponse response = RestAssured.given().log().all() + .body(request) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when() + .patch("/lines/1/stations/3/mid") + .then().log().all() + .extract(); + + // then + assertThat(response.statusCode()).isEqualTo(HttpStatus.NO_CONTENT.value()); + } + + @Test + @DisplayName("라인에 등록된 역을 삭제한다") + void delete() { + // given + final ConnectionInitRequest initRequest = new ConnectionInitRequest(2L, 10); + + RestAssured.given() + .body(initRequest) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when() + .patch("/lines/1/stations/1/init"); + + // when + final ExtractableResponse response = RestAssured.given() + .when() + .delete("/lines/1/stations/1") + .then().log().all() + .extract(); + + // then + assertThat(response.statusCode()).isEqualTo(HttpStatus.NO_CONTENT.value()); + } + + @Test + @DisplayName("해당 라인에 등록된 역을 조회한다") + void showStationsLineById() { + // given + final ConnectionInitRequest line1Request = new ConnectionInitRequest(2L, 10); + + RestAssured.given() + .body(line1Request) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when() + .patch("/lines/1/stations/1/init"); + + final ConnectionInitRequest line2Request = new ConnectionInitRequest(4L, 6); + + RestAssured.given() + .body(line2Request) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when() + .patch("/lines/2/stations/3/init"); + + // when + final ExtractableResponse response = RestAssured.given().log().all() + .accept(MediaType.APPLICATION_JSON_VALUE) + .when() + .get("/lines/1/stations") + .then().log().all() + .extract(); + + // then + final LineStationResponse lineStationResponse = response.as(LineStationResponse.class); + final List stationNames = lineStationResponse.getStationResponses() + .stream() + .map(StationResponse::getName) + .collect(Collectors.toList()); + assertAll( + () -> Assertions.assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()), + () -> Assertions.assertThat(stationNames).containsOnly("강남역", "잠실역"), + () -> Assertions.assertThat(lineStationResponse.getDistances()).containsOnly(10) + ); + } + + @DisplayName("모든 라인에 등록된 역들과 거리를 조회한다") + @Test + void showStations() { + // given + final ConnectionInitRequest line1Request = new ConnectionInitRequest(2L, 10); + + RestAssured.given() + .body(line1Request) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when() + .patch("/lines/1/stations/1/init"); + + final ConnectionInitRequest line2Request = new ConnectionInitRequest(4L, 6); + + RestAssured.given() + .body(line2Request) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when() + .patch("/lines/2/stations/3/init"); + + // when + final ExtractableResponse response = RestAssured.given().log().all() + .accept(MediaType.APPLICATION_JSON_VALUE) + .when() + .get("/lines/stations") + .then().log().all() + .extract(); + + // then + final List stationResponses = response.jsonPath().getList("stationResponses.flatten()", StationResponse.class); + final List stationNames = stationResponses.stream() + .map(StationResponse::getName) + .collect(Collectors.toList()); + final List distances = response.jsonPath().getList("distances.flatten()", String.class); + System.out.println(stationNames); + assertAll( + () -> Assertions.assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()), + () -> Assertions.assertThat(stationNames).contains("강남역", "잠실역", "건대입구역", "선릉역"), + () -> Assertions.assertThat(distances).contains("6", "10") + ); + } +} diff --git a/src/test/java/subway/integration/PathIntegrationTest.java b/src/test/java/subway/integration/PathIntegrationTest.java new file mode 100644 index 000000000..e7c5ff1af --- /dev/null +++ b/src/test/java/subway/integration/PathIntegrationTest.java @@ -0,0 +1,86 @@ +package subway.integration; + +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.assertj.core.api.Assertions; +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.springframework.test.context.jdbc.Sql; +import subway.dto.request.ConnectionEndpointRequest; +import subway.dto.request.ConnectionInitRequest; +import subway.dto.response.PathResponse; +import subway.ui.EndpointType; + +import java.util.List; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +@Sql("/dummy.sql") +public class PathIntegrationTest extends IntegrationTest { + + private static void patchEndpoint(final ConnectionEndpointRequest request, final String lineId, final String stationId) { + RestAssured.given() + .body(request) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when() + .patch("/lines/" + lineId + "/stations/" + stationId + "/endpoint"); + } + + private static void patchInit(final ConnectionInitRequest request, final String lineId, final String stationId) { + RestAssured.given() + .body(request) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when() + .patch("/lines/" + lineId + "/stations/" + stationId + "/init"); + } + + @BeforeEach + public void setUp() { + super.setUp(); + // 신분당선 : 강남역 -3- 잠실역 -2- 건대입구역 -1- 선릉역 + // 구신분당선 : 강남역 -2- 구의역 -1- 선릉역 + + final ConnectionInitRequest connectionInitRequest = new ConnectionInitRequest(2L, 10); + patchInit(connectionInitRequest, "1", "1"); + + final ConnectionEndpointRequest connectionDownRequest1 = new ConnectionEndpointRequest(EndpointType.DOWN.getValue(), 12); + patchEndpoint(connectionDownRequest1, "1", "3"); + + final ConnectionEndpointRequest connectionDownRequest2 = new ConnectionEndpointRequest(EndpointType.DOWN.getValue(), 12); + patchEndpoint(connectionDownRequest2, "1", "4"); + + final ConnectionInitRequest connectionInitRequest2 = new ConnectionInitRequest(5L, 12); + patchInit(connectionInitRequest2, "2", "1"); + + final ConnectionEndpointRequest connectionDownRequest3 = new ConnectionEndpointRequest(EndpointType.DOWN.getValue(), 11); + patchEndpoint(connectionDownRequest3, "2", "4"); + } + + @DisplayName("최단 경로를 조회하고 거리와 요금을 계산한다") + @Test + void findShortestPath() { + // when + final ExtractableResponse response = RestAssured.given().log().all() + .accept(MediaType.APPLICATION_JSON_VALUE) + .when() + .get("/path?source=1&target=4") + .then().log().all() + .extract(); + + // then + final PathResponse pathResponse = response.body().as(PathResponse.class); + final List stations = pathResponse.getStations(); + final int distance = pathResponse.getDistance(); + final int fare = pathResponse.getFare(); + + assertAll( + () -> Assertions.assertThat(stations).containsExactly("강남역", "구의역", "선릉역"), + () -> assertThat(distance).isEqualTo(23), + () -> assertThat(fare).isEqualTo(1550) + ); + } +} diff --git a/src/test/java/subway/integration/StationIntegrationTest.java b/src/test/java/subway/integration/StationIntegrationTest.java index a268f774e..92eb35709 100644 --- a/src/test/java/subway/integration/StationIntegrationTest.java +++ b/src/test/java/subway/integration/StationIntegrationTest.java @@ -7,7 +7,7 @@ import org.junit.jupiter.api.Test; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -import subway.dto.StationResponse; +import subway.dto.response.StationResponse; import java.util.HashMap; import java.util.List; @@ -23,11 +23,11 @@ public class StationIntegrationTest extends IntegrationTest { @Test void createStation() { // given - Map params = new HashMap<>(); + final Map params = new HashMap<>(); params.put("name", "강남역"); // when - ExtractableResponse response = RestAssured.given().log().all() + final ExtractableResponse response = RestAssured.given().log().all() .body(params) .contentType(MediaType.APPLICATION_JSON_VALUE) .when() @@ -44,7 +44,7 @@ void createStation() { @Test void createStationWithDuplicateName() { // given - Map params = new HashMap<>(); + final Map params = new HashMap<>(); params.put("name", "강남역"); RestAssured.given().log().all() .body(params) @@ -55,7 +55,7 @@ void createStationWithDuplicateName() { .extract(); // when - ExtractableResponse response = RestAssured.given().log().all() + final ExtractableResponse response = RestAssured.given().log().all() .body(params) .contentType(MediaType.APPLICATION_JSON_VALUE) .when() @@ -72,9 +72,9 @@ void createStationWithDuplicateName() { @Test void getStations() { /// given - Map params1 = new HashMap<>(); + final Map params1 = new HashMap<>(); params1.put("name", "강남역"); - ExtractableResponse createResponse1 = RestAssured.given().log().all() + final ExtractableResponse createResponse1 = RestAssured.given().log().all() .body(params1) .contentType(MediaType.APPLICATION_JSON_VALUE) .when() @@ -82,9 +82,9 @@ void getStations() { .then().log().all() .extract(); - Map params2 = new HashMap<>(); + final Map params2 = new HashMap<>(); params2.put("name", "역삼역"); - ExtractableResponse createResponse2 = RestAssured.given().log().all() + final ExtractableResponse createResponse2 = RestAssured.given().log().all() .body(params2) .contentType(MediaType.APPLICATION_JSON_VALUE) .when() @@ -93,7 +93,7 @@ void getStations() { .extract(); // when - ExtractableResponse response = RestAssured.given().log().all() + final ExtractableResponse response = RestAssured.given().log().all() .when() .get("/stations") .then().log().all() @@ -101,10 +101,10 @@ void getStations() { // then assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()); - List expectedStationIds = Stream.of(createResponse1, createResponse2) + final List expectedStationIds = Stream.of(createResponse1, createResponse2) .map(it -> Long.parseLong(it.header("Location").split("/")[2])) .collect(Collectors.toList()); - List resultStationIds = response.jsonPath().getList(".", StationResponse.class).stream() + final List resultStationIds = response.jsonPath().getList(".", StationResponse.class).stream() .map(StationResponse::getId) .collect(Collectors.toList()); assertThat(resultStationIds).containsAll(expectedStationIds); @@ -114,9 +114,9 @@ void getStations() { @Test void getStation() { /// given - Map params1 = new HashMap<>(); + final Map params1 = new HashMap<>(); params1.put("name", "강남역"); - ExtractableResponse createResponse = RestAssured.given().log().all() + final ExtractableResponse createResponse = RestAssured.given().log().all() .body(params1) .contentType(MediaType.APPLICATION_JSON_VALUE) .when() @@ -125,8 +125,8 @@ void getStation() { .extract(); // when - Long stationId = Long.parseLong(createResponse.header("Location").split("/")[2]); - ExtractableResponse response = RestAssured.given().log().all() + final Long stationId = Long.parseLong(createResponse.header("Location").split("/")[2]); + final ExtractableResponse response = RestAssured.given().log().all() .when() .get("/stations/{stationId}", stationId) .then().log().all() @@ -134,7 +134,7 @@ void getStation() { // then assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()); - StationResponse stationResponse = response.as(StationResponse.class); + final StationResponse stationResponse = response.as(StationResponse.class); assertThat(stationResponse.getId()).isEqualTo(stationId); } @@ -142,9 +142,9 @@ void getStation() { @Test void updateStation() { // given - Map params = new HashMap<>(); + final Map params = new HashMap<>(); params.put("name", "강남역"); - ExtractableResponse createResponse = RestAssured.given().log().all() + final ExtractableResponse createResponse = RestAssured.given().log().all() .body(params) .contentType(MediaType.APPLICATION_JSON_VALUE) .when() @@ -153,10 +153,10 @@ void updateStation() { .extract(); // when - Map otherParams = new HashMap<>(); + final Map otherParams = new HashMap<>(); otherParams.put("name", "삼성역"); - String uri = createResponse.header("Location"); - ExtractableResponse response = RestAssured.given().log().all() + final String uri = createResponse.header("Location"); + final ExtractableResponse response = RestAssured.given().log().all() .contentType(MediaType.APPLICATION_JSON_VALUE) .body(otherParams) .when() @@ -172,9 +172,9 @@ void updateStation() { @Test void deleteStation() { // given - Map params = new HashMap<>(); + final Map params = new HashMap<>(); params.put("name", "강남역"); - ExtractableResponse createResponse = RestAssured.given().log().all() + final ExtractableResponse createResponse = RestAssured.given().log().all() .body(params) .contentType(MediaType.APPLICATION_JSON_VALUE) .when() @@ -183,8 +183,8 @@ void deleteStation() { .extract(); // when - String uri = createResponse.header("Location"); - ExtractableResponse response = RestAssured.given().log().all() + final String uri = createResponse.header("Location"); + final ExtractableResponse response = RestAssured.given().log().all() .when() .delete(uri) .then().log().all() diff --git a/src/test/java/subway/persistance/LineDaoTest.java b/src/test/java/subway/persistance/LineDaoTest.java new file mode 100644 index 000000000..8628dfb03 --- /dev/null +++ b/src/test/java/subway/persistance/LineDaoTest.java @@ -0,0 +1,81 @@ +package subway.persistance; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.jdbc.Sql; +import subway.Fixture; +import subway.domain.Line; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@JdbcTest +@ContextConfiguration(classes = LineDao.class) +@Sql("/schema.sql") +class LineDaoTest { + + @Autowired + private LineDao lineDao; + + @Test + @DisplayName("노선을 추가한다") + void insert() { + // given & when + final Line inserted = lineDao.insert(Fixture.line1); + + // then + assertThat(inserted.getName()).isEqualTo(Fixture.line1.getName()); + assertThat(inserted.getColor()).isEqualTo(Fixture.line1.getColor()); + } + + @Test + @DisplayName("모든 노선을 조회한다") + void findAll() { + // given + lineDao.insert(Fixture.line1); + lineDao.insert(Fixture.line2); + lineDao.insert(Fixture.line3); + + // when + final List lines = lineDao.findAll(); + + // then + assertThat(lines.size()).isEqualTo(3); + } + + @Test + @DisplayName("id에 해당하는 노선을 조회한다") + void findById() { + // given + final Line inserted1 = lineDao.insert(Fixture.line1); + final Line inserted2 = lineDao.insert(Fixture.line2); + final Line inserted3 = lineDao.insert(Fixture.line3); + + // when + final Optional found = lineDao.findById(inserted2.getId()); + + // then + assertThat(found.get().getName()).isEqualTo(Fixture.line2.getName()); + assertThat(found.get().getColor()).isEqualTo(Fixture.line2.getColor()); + } + + @Test + @DisplayName("id에 해당하는 노선을 삭제한다") + void deleteById() { + // given + final Line inserted1 = lineDao.insert(Fixture.line1); + final Line inserted2 = lineDao.insert(Fixture.line2); + final Line inserted3 = lineDao.insert(Fixture.line3); + + // when + lineDao.deleteById(inserted2.getId()); + + // then + assertThat(lineDao.findAll().size()).isEqualTo(2); + } +} diff --git a/src/test/java/subway/persistance/LineRepositoryImplTest.java b/src/test/java/subway/persistance/LineRepositoryImplTest.java new file mode 100644 index 000000000..bc8be33a9 --- /dev/null +++ b/src/test/java/subway/persistance/LineRepositoryImplTest.java @@ -0,0 +1,119 @@ +package subway.persistance; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.jdbc.Sql; +import subway.Fixture; +import subway.domain.Line; +import subway.domain.LineRepository; +import subway.persistance.entity.SectionEntity; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@JdbcTest +@ContextConfiguration(classes = LineRepositoryImpl.class) +@Sql("/schema.sql") +class LineRepositoryImplTest { + + @Autowired + private LineRepository lineRepository; + @MockBean + private StationDao stationDao; + @MockBean + private LineDao lineDao; + @MockBean + private SectionDao sectionDao; + + @Test + @DisplayName("id로 라인을 조회한다") + void findById() { + // given + when(lineDao.findById(1L)).thenReturn(Optional.of(Fixture.line1)); + when(sectionDao.findByLineId(1L)).thenReturn( + List.of(new SectionEntity(Fixture.sectionAB, 1L, 0), + new SectionEntity(Fixture.sectionBC, 1L, 1)) + ); + when(stationDao.findById(1L)).thenReturn(Optional.of(Fixture.stationA)); + when(stationDao.findById(2L)).thenReturn(Optional.of(Fixture.stationB)); + when(stationDao.findById(3L)).thenReturn(Optional.of(Fixture.stationC)); + + // when + final Line found = lineRepository.findById(1L).get(); + + assertAll( + () -> assertThat(found.getName()).isEqualTo(Fixture.line1.getName()), + () -> assertThat(found.getColor()).isEqualTo(Fixture.line1.getColor()), + () -> Assertions.assertThat(found.getSections()).containsAll(Fixture.line1.getSections()) + ); + } + + @Test + @DisplayName("모든 라인을 조회한다") + void findAll() { + // given + when(lineDao.insert(any())).thenAnswer(invocation -> invocation.getArgument(0)); + when(lineDao.findAll()).thenReturn(List.of(Fixture.line1, Fixture.line2, Fixture.line3)); + when(lineDao.findById(1L)).thenReturn(Optional.of(Fixture.line1)); + when(lineDao.findById(2L)).thenReturn(Optional.of(Fixture.line2)); + when(lineDao.findById(3L)).thenReturn(Optional.of(Fixture.line3)); + when(sectionDao.findByLineId(1L)).thenReturn( + List.of(new SectionEntity(Fixture.sectionAB, 1L, 0), + new SectionEntity(Fixture.sectionBC, 1L, 1)) + ); + when(sectionDao.findByLineId(2L)).thenReturn( + List.of(new SectionEntity(Fixture.sectionAB, 2L, 0) + )); + when(sectionDao.findByLineId(3L)).thenReturn( + List.of(new SectionEntity(Fixture.sectionBC, 3L, 1)) + ); + when(stationDao.findById(1L)).thenReturn(Optional.of(Fixture.stationA)); + when(stationDao.findById(2L)).thenReturn(Optional.of(Fixture.stationB)); + when(stationDao.findById(3L)).thenReturn(Optional.of(Fixture.stationC)); + + // when + final List lines = lineRepository.findAll().getLines(); + + // then + assertThat(lines.size()).isEqualTo(3); + } + + @Test + @DisplayName("라인을 저장한다") + void save() { + // given + when(lineDao.insert(any())).thenReturn(Fixture.line1); + + // when + final Line saved = lineRepository.save(Fixture.line1); + + assertAll( + () -> assertThat(saved.getName()).isEqualTo(Fixture.line1.getName()), + () -> assertThat(saved.getColor()).isEqualTo(Fixture.line1.getColor()), + () -> Assertions.assertThat(saved.getSections()).containsAll(Fixture.line1.getSections()) + ); + } + + @Test + @DisplayName("id에 해당하는 라인을 삭제한다") + void delete() { + // when + lineRepository.delete(Fixture.line1); + + // then + verify(lineDao, times(1)).deleteById(1L); + verify(sectionDao, times(1)).deleteByLineId(1L); + } +} \ No newline at end of file diff --git a/src/test/java/subway/persistance/SectionDaoTest.java b/src/test/java/subway/persistance/SectionDaoTest.java new file mode 100644 index 000000000..7d5f649b6 --- /dev/null +++ b/src/test/java/subway/persistance/SectionDaoTest.java @@ -0,0 +1,103 @@ +package subway.persistance; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.jdbc.Sql; +import subway.domain.Distance; +import subway.domain.Line; +import subway.domain.Section; +import subway.domain.Sections; +import subway.domain.Station; +import subway.persistance.entity.SectionEntity; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +@JdbcTest +@ContextConfiguration(classes = SectionDao.class) +@Sql("/schema.sql") +class SectionDaoTest { + + @Autowired + private SectionDao sectionDao; + + @Test + @DisplayName("section을 저장한다") + void insertSection() { + // given + final Station stationA = new Station(1L, "A"); + final Station stationB = new Station(2L, "B"); + final Section section = new Section(stationA, stationB, new Distance(2)); + final Line line1 = new Line(1L, "1호선", "blue", new Sections()); + + final SectionEntity sectionEntity = new SectionEntity(section, line1.getId(), 0); + + // when + sectionDao.insertSection(sectionEntity); + + // then + final SectionEntity found = sectionDao.findByLineId(line1.getId()).get(0); + assertAll( + () -> assertThat(found.getUpStationId()).isEqualTo(stationA.getId()), + () -> assertThat(found.getDownStationId()).isEqualTo(stationB.getId()), + () -> assertThat(found.getDistance()).isEqualTo(section.getDistance().getValue()), + () -> assertThat(found.getLineId()).isEqualTo(line1.getId()) + ); + } + + @Test + @DisplayName("section을 id로 조회한다") + void findByLineId() { + // given + final Station stationA = new Station(1L, "A"); + final Station stationB = new Station(2L, "B"); + final Station stationC = new Station(3L, "C"); + final Section sectionAB = new Section(stationA, stationB, new Distance(2)); + final Section sectionBC = new Section(stationB, stationC, new Distance(2)); + final Line line1 = new Line(1L, "1호선", "blue", new Sections()); + + final SectionEntity sectionEntity = new SectionEntity(sectionAB, line1.getId(), 0); + + sectionDao.insertSection(new SectionEntity(sectionBC, 2L, 0)); + sectionDao.insertSection(sectionEntity); + sectionDao.insertSection(new SectionEntity(sectionBC, 3L, 0)); + + // when + final SectionEntity found = sectionDao.findByLineId(line1.getId()).get(0); + + // then + assertAll( + () -> assertThat(found.getUpStationId()).isEqualTo(stationA.getId()), + () -> assertThat(found.getDownStationId()).isEqualTo(stationB.getId()), + () -> assertThat(found.getDistance()).isEqualTo(sectionAB.getDistance().getValue()), + () -> assertThat(found.getLineId()).isEqualTo(line1.getId()) + ); + } + + @Test + @DisplayName("id에 해당하는 section을 삭제한다") + void deleteByLineId() { + // given + final Station stationA = new Station(1L, "A"); + final Station stationB = new Station(2L, "B"); + final Station stationC = new Station(3L, "C"); + final Section sectionAB = new Section(stationA, stationB, new Distance(2)); + final Section sectionBC = new Section(stationB, stationC, new Distance(2)); + final Line line1 = new Line(1L, "1호선", "blue", new Sections()); + + final SectionEntity sectionEntity = new SectionEntity(sectionAB, line1.getId(), 0); + + sectionDao.insertSection(new SectionEntity(sectionBC, 2L, 0)); + sectionDao.insertSection(sectionEntity); + sectionDao.insertSection(new SectionEntity(sectionBC, 3L, 0)); + + // when + sectionDao.deleteByLineId(line1.getId()); + + // then + assertThat(sectionDao.findByLineId(line1.getId()).size()).isEqualTo(0); + } +} diff --git a/src/test/java/subway/persistance/StationDaoTest.java b/src/test/java/subway/persistance/StationDaoTest.java new file mode 100644 index 000000000..18e267e3d --- /dev/null +++ b/src/test/java/subway/persistance/StationDaoTest.java @@ -0,0 +1,94 @@ +package subway.persistance; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.jdbc.Sql; +import subway.Fixture; +import subway.domain.Station; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@JdbcTest +@ContextConfiguration(classes = StationDao.class) +@Sql("/schema.sql") +class StationDaoTest { + + @Autowired + private StationDao stationDao; + + @Test + @DisplayName("A역을 저장한다") + void insert() { + // given & when + final Station insert = stationDao.insert(Fixture.stationA); + + // then + final Station station = stationDao.findById(insert.getId()).get(); + assertThat(station).isEqualTo(insert); + } + + @Test + @DisplayName("A,B,C역을 저장하고 모두 조회한다") + void findAll() { + // given + final Station insertA = stationDao.insert(Fixture.stationA); + final Station insertB = stationDao.insert(Fixture.stationB); + final Station insertC = stationDao.insert(Fixture.stationC); + + // when + final List stations = stationDao.findAll(); + + // then + Assertions.assertThat(stations).containsOnly(insertA, insertB, insertC); + } + + @Test + @DisplayName("A,B,C역을 저장하고 B를 Id로 조회한다") + void findById() { + // given + final Station insertA = stationDao.insert(Fixture.stationA); + final Station insertB = stationDao.insert(Fixture.stationB); + final Station insertC = stationDao.insert(Fixture.stationC); + + // when + final Optional found = stationDao.findById(insertB.getId()); + + // then + Assertions.assertThat(found.get()).isEqualTo(insertB); + } + + @Test + @DisplayName("A역을 업데이트한다") + void update() { + // given + final Station insertA = stationDao.insert(Fixture.stationA); + final Station updatedStation = new Station(insertA.getId(), "잠실나루역"); + + // when + stationDao.update(updatedStation); + + // then + final Optional found = stationDao.findById(insertA.getId()); + Assertions.assertThat(found.get()).isEqualTo(updatedStation); + } + + @Test + @DisplayName("A역을 삭제한다") + void deleteById() { + // given + final Station insertA = stationDao.insert(Fixture.stationA); + + // when + stationDao.deleteById(insertA.getId()); + + // then + Assertions.assertThat(stationDao.findAll().size()).isEqualTo(0); + } +} diff --git a/src/test/java/subway/ui/LineControllerTest.java b/src/test/java/subway/ui/LineControllerTest.java new file mode 100644 index 000000000..ad7eff505 --- /dev/null +++ b/src/test/java/subway/ui/LineControllerTest.java @@ -0,0 +1,131 @@ +package subway.ui; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import subway.application.LineService; +import subway.dto.request.LineRequest; +import subway.dto.response.LineResponse; + +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.refEq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(LineController.class) +class LineControllerTest { + + @Autowired + private MockMvc mockMvc; + @Autowired + private ObjectMapper objectMapper; + @MockBean + private LineService lineService; + + private LineRequest request1; + private LineRequest request2; + private LineRequest request3; + private LineResponse response1; + private LineResponse response2; + private LineResponse response3; + + @BeforeEach + void setUp() { + request1 = new LineRequest("1호선", "blue"); + request2 = new LineRequest("2호선", "green"); + request3 = new LineRequest("3호선", "orange"); + response1 = new LineResponse(1L, "1호선", "blue"); + response2 = new LineResponse(2L, "2호선", "green"); + response3 = new LineResponse(3L, "3호선", "orange"); + } + + @Test + @DisplayName("post /lines : created를 반환하고 Location에 uri를 저장한다") + void createLine() throws Exception { + // given + final String jsonRequest = objectMapper.writeValueAsString(request1); + when(lineService.createLine(any())).thenReturn(response1); + + // when & then + mockMvc.perform(post("/lines") + .contentType(MediaType.APPLICATION_JSON) + .content(jsonRequest)) + .andExpect(status().isCreated()) + .andExpect(header().string("Location", "/lines/1")); + + verify(lineService, times(1)).createLine(refEq(request1)); + } + + @Test + @DisplayName("get /lines : ok를 반환하고 모든 LineResponse를 반환한다") + void findAllLines() throws Exception { + // given + final List responses = List.of(response1, response2, response3); + final String jsonResponses = objectMapper.writeValueAsString(responses); + when(lineService.findLineResponses()).thenReturn(responses); + + // when & then + mockMvc.perform(get("/lines") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().json(jsonResponses)); + + verify(lineService, times(1)).findLineResponses(); + } + + @Test + @DisplayName("get /lines/{id} : ok를 반환하고 id에 해당하는 stationResponse를 반환한다") + void findLineById() throws Exception { + // given + when(lineService.findLineResponseById(any())).thenReturn(response1); + final String jsonResponse = objectMapper.writeValueAsString(response1); + + // when & then + mockMvc.perform(get("/lines/1") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().json(jsonResponse)); + + verify(lineService, times(1)).findLineResponseById(1L); + } + + @Test + @DisplayName("put /lines/{id} : ok를 반환한다") + void updateLine() throws Exception { + // given + final String jsonRequest = objectMapper.writeValueAsString(request1); + + // when & then + mockMvc.perform(put("/lines/1") + .contentType(MediaType.APPLICATION_JSON) + .content(jsonRequest)) + .andExpect(status().isOk()); + + verify(lineService, times(1)).updateLine(any(), any()); + } + + @Test + @DisplayName("delete /stations/{id} : noContent를 반환한다") + void deleteLine() throws Exception { + mockMvc.perform(delete("/lines/1")) + .andExpect(status().isNoContent()); + + verify(lineService, times(1)).deleteLineById(1L); + } +} \ No newline at end of file diff --git a/src/test/java/subway/ui/LineStationControllerTest.java b/src/test/java/subway/ui/LineStationControllerTest.java new file mode 100644 index 000000000..6b2e2e8a2 --- /dev/null +++ b/src/test/java/subway/ui/LineStationControllerTest.java @@ -0,0 +1,187 @@ +package subway.ui; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import subway.application.LineStationService; +import subway.dto.request.ConnectionEndpointRequest; +import subway.dto.request.ConnectionInitRequest; +import subway.dto.request.ConnectionMidRequest; +import subway.dto.response.LineStationResponse; +import subway.dto.response.StationResponse; + +import java.util.List; + +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(LineStationController.class) +class LineStationControllerTest { + + @Autowired + private MockMvc mockMvc; + @Autowired + private ObjectMapper objectMapper; + @MockBean + private LineStationService lineStationService; + + @Test + @DisplayName("patch /lines/{lineId}/stations/{stationId}/init : noContent를 반환한다.") + void addStationToLine_init() throws Exception { + // given + final ConnectionInitRequest request = new ConnectionInitRequest(2L, 1); + final String jsonRequest = objectMapper.writeValueAsString(request); + + //when & then + mockMvc.perform(patch("/lines/1/stations/1/init") + .contentType(MediaType.APPLICATION_JSON) + .content(jsonRequest)) + .andExpect(status().isNoContent()); + + verify(lineStationService, times(1)) + .addInitStations( + 1L, + 1L, + request.getNextStationId(), + request.getDistance() + ); + } + + @Test + @DisplayName("patch /lines/{lineId}/stations/{stationId}/endpoint : noContent를 반환한다.") + void addStationToLine_up() throws Exception { + // given + final ConnectionEndpointRequest request = new ConnectionEndpointRequest(EndpointType.UP.getValue(), 1); + final String jsonRequest = objectMapper.writeValueAsString(request); + + //when & then + mockMvc.perform(patch("/lines/1/stations/1/endpoint") + .contentType(MediaType.APPLICATION_JSON) + .content(jsonRequest)) + .andExpect(status().isNoContent()); + + verify(lineStationService, times(1)) + .addEndpoint( + request.getEndpointType(), + 1L, + 1L, + request.getDistance() + ); + } + + @Test + @DisplayName("patch /lines/{lineId}/stations/{stationId}/endpoint : noContent를 반환한다.") + void addStationToLine_down() throws Exception { + // given + final ConnectionEndpointRequest request = new ConnectionEndpointRequest(EndpointType.DOWN.getValue(), 1); + final String jsonRequest = objectMapper.writeValueAsString(request); + + //when & then + mockMvc.perform(patch("/lines/1/stations/1/endpoint") + .contentType(MediaType.APPLICATION_JSON) + .content(jsonRequest)) + .andExpect(status().isNoContent()); + + verify(lineStationService, times(1)) + .addEndpoint( + request.getEndpointType(), + 1L, + 1L, + request.getDistance() + ); + } + + @Test + @DisplayName("patch /lines/{lineId}/stations/{stationId}/mid : noContent를 반환한다.") + void addStationToLine_mid() throws Exception { + // given + final ConnectionMidRequest request = new ConnectionMidRequest(2L, 1); + final String jsonRequest = objectMapper.writeValueAsString(request); + + //when & then + mockMvc.perform(patch("/lines/1/stations/1/mid") + .contentType(MediaType.APPLICATION_JSON) + .content(jsonRequest)) + .andExpect(status().isNoContent()); + + verify(lineStationService, times(1)) + .addIntermediate( + 1L, + 1L, + request.getPrevStationId(), + request.getDistance() + ); + } + + @DisplayName("lineId와 stationId에 해당하는 역 연결 정보를 삭제한다") + @Test + void deleteStationById() throws Exception { + mockMvc.perform(delete("/lines/1/stations/1")) + .andExpect(status().isNoContent()); + + verify(lineStationService, times(1)).deleteStationInLine(1L, 1L); + } + + @Test + @DisplayName("get /lines/{lineId}/stations : 라인에 해당하는 모든 역을 조회한다.") + void showStationsByLineId() throws Exception { + // given + final List stationResponses = List.of( + new StationResponse(1L, "선릉역"), + new StationResponse(2L, "강남역"), + new StationResponse(3L, "잠실역")); + final List distances = List.of(1, 3); + final LineStationResponse response = new LineStationResponse(stationResponses, distances); + final String jsonResponse = objectMapper.writeValueAsString(response); + + when(lineStationService.findByLineId(1L)).thenReturn(response); + + // when & then + mockMvc.perform(get("/lines/1/stations") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().json(jsonResponse)); + + verify(lineStationService, times(1)).findByLineId(1L); + } + + @DisplayName("get /lines/stations : 모든 역을 조회한다") + @Test + void showStations() throws Exception { + // given + final List stationResponses1 = List.of( + new StationResponse(1L, "선릉역"), + new StationResponse(2L, "강남역"), + new StationResponse(3L, "잠실역")); + final List distances1 = List.of(1, 3); + + final List stationResponses2 = List.of( + new StationResponse(4L, "구의역"), + new StationResponse(5L, "건대입구역"), + new StationResponse(6L, "신도림역")); + final List distances2 = List.of(5, 8); + + final LineStationResponse response = new LineStationResponse(stationResponses1, distances1); + final LineStationResponse response2 = new LineStationResponse(stationResponses2, distances2); + final List responses = List.of(response, response2); + final String jsonResponse = objectMapper.writeValueAsString(responses); + + when(lineStationService.findAll()).thenReturn(responses); + + // when & then + mockMvc.perform(get("/lines/stations") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(content().json(jsonResponse)); + } +} \ No newline at end of file diff --git a/src/test/java/subway/ui/StationControllerTest.java b/src/test/java/subway/ui/StationControllerTest.java new file mode 100644 index 000000000..251665902 --- /dev/null +++ b/src/test/java/subway/ui/StationControllerTest.java @@ -0,0 +1,130 @@ +package subway.ui; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import subway.application.LineStationService; +import subway.application.StationService; +import subway.dto.request.StationRequest; +import subway.dto.response.StationResponse; + +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(StationController.class) +class StationControllerTest { + + @Autowired + private MockMvc mockMvc; + @Autowired + private ObjectMapper objectMapper; + @MockBean + private StationService stationService; + @MockBean + private LineStationService lineStationService; + + @Test + @DisplayName("post /stations : created를 반환하고 Location에 uri를 저장한") + void createStation() throws Exception { + // given + final StationRequest request = new StationRequest("잠실역"); + final StationResponse response = new StationResponse(1L, "잠실역"); + + final String jsonRequest = objectMapper.writeValueAsString(request); + + when(stationService.saveStation(any())).thenReturn(response); + + // when & then + mockMvc.perform(post("/stations") + .contentType(MediaType.APPLICATION_JSON) + .content(jsonRequest)) + .andExpect(status().isCreated()) + .andExpect(header().string("Location", "/stations/1")); + + verify(stationService, times(1)).saveStation(any()); + } + + @Test + @DisplayName("get /stations : ok를 반환하고 모든 StationResponse를 반환한다") + void showStations() throws Exception { + // given + final StationResponse response1 = new StationResponse(1L, "잠실역"); + final StationResponse response2 = new StationResponse(2L, "선릉역"); + final StationResponse response3 = new StationResponse(3L, "강남역"); + final List responses = List.of(response1, response2, response3); + final String jsonResponse = objectMapper.writeValueAsString(responses); + + when(stationService.findAllStationResponses()).thenReturn(responses); + + // when & then + mockMvc.perform(get("/stations") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().json(jsonResponse)); + + verify(stationService, times(1)).findAllStationResponses(); + } + + @Test + @DisplayName("get /stations/{id} : ok를 반환하고 id에 해당하는 stationResponse를 반환한다") + void showStation() throws Exception { + // given + final StationResponse response1 = new StationResponse(1L, "잠실역"); + final StationResponse response2 = new StationResponse(2L, "선릉역"); + final StationResponse response3 = new StationResponse(3L, "강남역"); + final String jsonResponse = objectMapper.writeValueAsString(response2); + + when(stationService.findStationResponseById(2L)).thenReturn(response2); + + // when & then + mockMvc.perform(get("/stations/2") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().json(jsonResponse)); + + verify(stationService, times(1)).findStationResponseById(2L); + } + + @Test + @DisplayName("put /stations/{id} : ok를 반환한다") + void updateStation() throws Exception { + // given + final StationRequest request = new StationRequest("선릉역"); + + final String jsonRequest = objectMapper.writeValueAsString(request); + + // when & then + mockMvc.perform(put("/stations/1") + .contentType(MediaType.APPLICATION_JSON) + .content(jsonRequest)) + .andExpect(status().isOk()); + + verify(stationService, times(1)).updateStation(any(), any()); + } + + @Test + @DisplayName("delete /stations/{id} : noContent를 반환한다") + void deleteStation() throws Exception { + // when & then + mockMvc.perform(delete("/stations/1")) + .andExpect(status().isNoContent()); + + verify(lineStationService, times(1)).deleteStation(1L); + } +} \ No newline at end of file diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties new file mode 100644 index 000000000..ffabf1153 --- /dev/null +++ b/src/test/resources/application.properties @@ -0,0 +1,3 @@ +spring.datasource.url=jdbc:h2:mem:test +spring.datasource.driver-class-name=org.h2.Driver +spring.h2.console.enabled=true diff --git a/src/test/resources/dummy.sql b/src/test/resources/dummy.sql new file mode 100644 index 000000000..b3dd18db7 --- /dev/null +++ b/src/test/resources/dummy.sql @@ -0,0 +1,16 @@ +INSERT INTO line(name, color) +VALUES ('신분당선', 'bg-red-600'); +INSERT INTO line(name, color) +VALUES ('구신분당선', 'bg-red-600'); + +INSERT INTO station(name) +VALUES ('강남역'); +INSERT INTO station(name) +VALUES ('잠실역'); +INSERT INTO station(name) +VALUES ('건대입구역'); +INSERT INTO station(name) +VALUES ('선릉역'); +INSERT INTO station(name) +VALUES ('구의역'); +