diff --git a/.gitignore b/.gitignore index 6c10f4ae2..c5404ce53 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,10 @@ out/ ### VS Code ### .vscode/ -frontend/node_modules \ No newline at end of file +frontend/node_modules + +### OS ### +.DS_Store +._.DS_Store +**/.DS_Store +**/._.DS_Store \ No newline at end of file diff --git a/build.gradle b/build.gradle index 8c438cd91..069df76ee 100644 --- a/build.gradle +++ b/build.gradle @@ -1,14 +1,9 @@ plugins { id 'org.springframework.boot' version '2.7.1' id 'io.spring.dependency-management' version '1.0.11.RELEASE' - id "org.asciidoctor.jvm.convert" version "3.3.2" id 'java' } -configurations { - asciidoctorExt -} - group = 'nextstep' version = '0.0.1-SNAPSHOT' sourceCompatibility = '11' @@ -17,6 +12,7 @@ repositories { mavenCentral() } + dependencies { // spring implementation 'org.springframework.boot:spring-boot-starter-web' @@ -31,49 +27,23 @@ dependencies { // jwt implementation 'io.jsonwebtoken:jjwt:0.9.1' - // rest docs - asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor:2.0.5.RELEASE' - testImplementation 'org.springframework.restdocs:spring-restdocs-restassured:2.0.5.RELEASE' - // test testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'io.rest-assured:rest-assured:4.5.1' - runtimeOnly 'com.h2database:h2' -} + // cucumber + testImplementation("io.cucumber:cucumber-java:7.14.0") + testImplementation("io.cucumber:cucumber-java8:7.14.0") + testImplementation("io.cucumber:cucumber-spring:7.14.0") + testImplementation("io.cucumber:cucumber-junit-platform-engine:7.14.0") + testImplementation("org.junit.platform:junit-platform-suite:1.10.0") + testImplementation("org.junit.platform:junit-platform-suite-api:1.10.0") + testImplementation("org.junit.platform:junit-platform-commons:1.10.0") + testImplementation("org.junit.platform:junit-platform-engine:1.10.0") -ext { - snippetsDir = file('build/generated-snippets') + runtimeOnly 'com.h2database:h2' } test { useJUnitPlatform() - outputs.dir snippetsDir -} - -task testDocument(type: Test) { - useJUnitPlatform() - filter { - includeTestsMatching "*.documentation.*" - } -} - -asciidoctor { - inputs.dir snippetsDir - configurations 'asciidoctorExt' - dependsOn test -} - -bootJar { - dependsOn asciidoctor - from("${asciidoctor.outputDir}/html5") { - into 'static/docs' - } -} - -task copyDocument(type: Copy) { - dependsOn asciidoctor - - from file("build/docs/asciidoc") - into file("src/main/resources/static/docs") } diff --git a/src/main/resources/application-test.properties b/src/main/resources/application-test.properties index c20b3bf91..431ba63c4 100644 --- a/src/main/resources/application-test.properties +++ b/src/main/resources/application-test.properties @@ -7,4 +7,4 @@ security.jwt.token.expire-length= 3600000 github.client.id= test_id github.client.secret= test_secret github.url.access-token= http://localhost:8080/github/login/oauth/access_token -github.url.profile= http://localhost:8080/github/user \ No newline at end of file +github.url.profile= http://localhost:8080/github/user diff --git a/src/test/java/nextstep/cucumber/CucumberTest.java b/src/test/java/nextstep/cucumber/CucumberTest.java new file mode 100644 index 000000000..6bff6403f --- /dev/null +++ b/src/test/java/nextstep/cucumber/CucumberTest.java @@ -0,0 +1,23 @@ +package nextstep.cucumber; + +import io.cucumber.spring.CucumberContextConfiguration; +import org.junit.platform.suite.api.ConfigurationParameter; +import org.junit.platform.suite.api.IncludeEngines; +import org.junit.platform.suite.api.SelectClasspathResource; +import org.junit.platform.suite.api.Suite; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME; +import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME; + +@ActiveProfiles("test") +@Suite +@IncludeEngines("cucumber") +@SelectClasspathResource("features") +@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "nextstep") +@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty") +@CucumberContextConfiguration +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) +public class CucumberTest { +} diff --git a/src/test/java/nextstep/cucumber/steps/AcceptanceContext.java b/src/test/java/nextstep/cucumber/steps/AcceptanceContext.java new file mode 100644 index 000000000..11293d7f0 --- /dev/null +++ b/src/test/java/nextstep/cucumber/steps/AcceptanceContext.java @@ -0,0 +1,26 @@ +package nextstep.cucumber.steps; + +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + + +@Component +public class AcceptanceContext { + public Map store = new HashMap<>(); + public ExtractableResponse response; + + public void add(ExtractableResponse response) { + store.put(response.jsonPath().getString("name"), + response.jsonPath().getLong("id")); + } + + public Long get(String name) { + return store.get(name); + } +} + + diff --git a/src/test/java/nextstep/cucumber/steps/PathStepDef.java b/src/test/java/nextstep/cucumber/steps/PathStepDef.java new file mode 100644 index 000000000..9d8d72601 --- /dev/null +++ b/src/test/java/nextstep/cucumber/steps/PathStepDef.java @@ -0,0 +1,74 @@ +package nextstep.cucumber.steps; + +import io.cucumber.datatable.DataTable; +import io.cucumber.java8.En; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import nextstep.subway.acceptance.PathSteps; +import nextstep.subway.domain.PathRequestType; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static nextstep.subway.acceptance.LineSteps.지하철_노선_생성_요청; +import static nextstep.subway.acceptance.LineSteps.지하철_노선에_지하철_구간_생성_요청; +import static nextstep.subway.acceptance.StationSteps.지하철역_생성_요청; +import static org.assertj.core.api.Assertions.assertThat; + + +public class PathStepDef implements En { + + + @Autowired + private AcceptanceContext context; + ExtractableResponse response; + + public PathStepDef() { + + Given("지하철역들을 생성하고", (DataTable table) -> { + List names = table.asList(); + names.forEach(name -> context.add(지하철역_생성_요청(name))); + }); + + Given("지하철 노선들을 생성하고", (DataTable table) -> { + List> lines = table.asMaps(); + lines.forEach(this::createLine); + }); + + Given("{string}에 지하철 역을 추가하고", (String lineName, DataTable table) -> { + Long lineId = context.get(lineName); + table.asMaps().forEach(add -> addStationInLine(add, lineId)); + }); + When("{string}과 {string} 사이 경로를 조회하면", (String upStation, String downStation) + -> response = PathSteps.두_역의_거리_경로_조회를_요청( + context.get(upStation), context.get(downStation), PathRequestType.DISTANCE + )); + Then("{string}-{string}-{string} 경로가 조회된다", (String upStation, String middleStation, String downStation) + -> assertThat(response.jsonPath().getList("stations.name", String.class)) + .containsExactly(upStation, middleStation, downStation)); + } + + private void addStationInLine(Map add, Long lineId) { + Map params = new HashMap<>(); + params.put("upStationId", context.get(add.get("upStation")) + ""); + params.put("downStationId", context.get(add.get("downStation")) + ""); + params.put("distance", add.get("distance")); + params.put("duration", add.get("duration")); + 지하철_노선에_지하철_구간_생성_요청(lineId, params); + } + + private void createLine(Map line) { + Map params = new HashMap<>(); + params.put("name", line.get("name")); + params.put("color", line.get("color")); + params.put("upStationId", context.get(line.get("upStation")) + ""); + params.put("downStationId", context.get(line.get("downStation")) + ""); + params.put("distance", line.get("distance")); + params.put("duration", line.get("duration")); + params.put("surcharge", line.get("surcharge")); + var response = 지하철_노선_생성_요청(params); + context.add(response); + } +} diff --git a/src/test/java/nextstep/cucumber/steps/StationStepDef.java b/src/test/java/nextstep/cucumber/steps/StationStepDef.java new file mode 100644 index 000000000..5273f09e3 --- /dev/null +++ b/src/test/java/nextstep/cucumber/steps/StationStepDef.java @@ -0,0 +1,48 @@ +package nextstep.cucumber.steps; + +import io.cucumber.java8.En; +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + + +public class StationStepDef implements En { + ExtractableResponse response; + + public StationStepDef() { + When("지하철역을 생성하면", () -> { + Map params = new HashMap<>(); + params.put("name", "강남역"); + response = RestAssured.given().log().all() + .body(params) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when() + .post("/stations") + .then().log().all() + .extract(); + }); + + Then("지하철역이 생성된다", () -> { + assertThat(response.statusCode()).isEqualTo(HttpStatus.CREATED.value()); + }); + + Then("지하철역 목록 조회 시 생성한 역을 찾을 수 있다", () -> { + List stationNames = + RestAssured.given().log().all() + .when().get("/stations") + .then().log().all() + .extract().jsonPath().getList("name", String.class); + assertThat(stationNames).containsAnyOf("강남역"); + }); + + } + +} diff --git a/src/test/java/nextstep/study/JgraphtTest.java b/src/test/java/nextstep/study/JgraphtTest.java deleted file mode 100644 index ce58ec1a1..000000000 --- a/src/test/java/nextstep/study/JgraphtTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package nextstep.study; - -import org.jgrapht.GraphPath; -import org.jgrapht.alg.shortestpath.DijkstraShortestPath; -import org.jgrapht.alg.shortestpath.KShortestPaths; -import org.jgrapht.graph.DefaultWeightedEdge; -import org.jgrapht.graph.WeightedMultigraph; -import org.junit.jupiter.api.Test; - -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - - -class JgraphtTest { - @Test - void getDijkstraShortestPath() { - String source = "v3"; - String target = "v1"; - - WeightedMultigraph graph = new WeightedMultigraph(DefaultWeightedEdge.class); - graph.addVertex("v1"); - graph.addVertex("v2"); - graph.addVertex("v3"); - graph.setEdgeWeight(graph.addEdge("v1", "v2"), 2); - graph.setEdgeWeight(graph.addEdge("v2", "v3"), 2); - graph.setEdgeWeight(graph.addEdge("v1", "v3"), 100); - - DijkstraShortestPath dijkstraShortestPath = new DijkstraShortestPath(graph); - List shortestPath = dijkstraShortestPath.getPath(source, target).getVertexList(); - - assertThat(shortestPath.size()).isEqualTo(3); - } - - @Test - void getKShortestPaths() { - String source = "v3"; - String target = "v1"; - - WeightedMultigraph graph = new WeightedMultigraph(DefaultWeightedEdge.class); - graph.addVertex("v1"); - graph.addVertex("v2"); - graph.addVertex("v3"); - graph.setEdgeWeight(graph.addEdge("v1", "v2"), 2); - graph.setEdgeWeight(graph.addEdge("v2", "v3"), 2); - graph.setEdgeWeight(graph.addEdge("v1", "v3"), 100); - - List paths = new KShortestPaths(graph, 100).getPaths(source, target); - - assertThat(paths).hasSize(2); - paths.stream() - .forEach(it -> { - assertThat(it.getVertexList()).startsWith(source); - assertThat(it.getVertexList()).endsWith(target); - }); - } -} \ No newline at end of file diff --git a/src/test/java/nextstep/subway/documentation/Documentation.java b/src/test/java/nextstep/subway/documentation/Documentation.java deleted file mode 100644 index dbd2a9844..000000000 --- a/src/test/java/nextstep/subway/documentation/Documentation.java +++ /dev/null @@ -1,31 +0,0 @@ -package nextstep.subway.documentation; - -import io.restassured.RestAssured; -import io.restassured.builder.RequestSpecBuilder; -import io.restassured.specification.RequestSpecification; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.restdocs.RestDocumentationContextProvider; -import org.springframework.restdocs.RestDocumentationExtension; -import org.springframework.test.context.ActiveProfiles; - -import static org.springframework.restdocs.restassured3.RestAssuredRestDocumentation.documentationConfiguration; - -@ActiveProfiles("test") -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@ExtendWith(RestDocumentationExtension.class) -public class Documentation { - protected RequestSpecification spec; - @LocalServerPort - private int port; - - @BeforeEach - public void setUp(RestDocumentationContextProvider restDocumentation) { - RestAssured.port = port; - this.spec = new RequestSpecBuilder() - .addFilter(documentationConfiguration(restDocumentation)) - .build(); - } -} diff --git a/src/test/java/nextstep/subway/documentation/PathDocumentation.java b/src/test/java/nextstep/subway/documentation/PathDocumentation.java deleted file mode 100644 index 174a42f9f..000000000 --- a/src/test/java/nextstep/subway/documentation/PathDocumentation.java +++ /dev/null @@ -1,79 +0,0 @@ -package nextstep.subway.documentation; - -import io.restassured.response.ExtractableResponse; -import io.restassured.response.Response; -import nextstep.subway.applicaion.PathService; -import nextstep.subway.applicaion.dto.PathResponse; -import nextstep.subway.applicaion.dto.StationResponse; -import nextstep.subway.domain.PathRequestType; -import org.assertj.core.api.Assertions; -import org.assertj.core.util.Lists; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.http.HttpStatus; -import org.springframework.restdocs.payload.JsonFieldType; -import org.springframework.restdocs.payload.ResponseFieldsSnippet; -import org.springframework.restdocs.request.RequestParametersSnippet; - -import static nextstep.subway.acceptance.PathSteps.경로_조회; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.when; -import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; -import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; -import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; -import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; -import static org.springframework.restdocs.request.RequestDocumentation.requestParameters; -import static org.springframework.restdocs.restassured3.RestAssuredRestDocumentation.document; - -@ExtendWith(MockitoExtension.class) -class PathDocumentation extends Documentation { - - @MockBean - private PathService pathService; - - @Test - void path() { - PathResponse pathResponse = new PathResponse( - Lists.newArrayList( - new StationResponse(1L, "교대역"), - new StationResponse(2L, "남부터미널역"), - new StationResponse(3L, "양재역") - ), 10, 20, 1450); - - - when(pathService.findPath(anyLong(), anyLong(), any(PathRequestType.class), anyInt())).thenReturn(pathResponse); - - this.spec.filter(document("path", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - setPathRequestParametersDescription(), - setPathResponseFieldsDescription() - )); - - ExtractableResponse response = 경로_조회(spec, 1L, 3L, PathRequestType.DISTANCE); - - Assertions.assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()); - - } - - private RequestParametersSnippet setPathRequestParametersDescription() { - return requestParameters( - parameterWithName("source").description("출발역 식별자"), - parameterWithName("target").description("도착역 식별자"), - parameterWithName("type").description("조회구분코드") - ); - } - - private ResponseFieldsSnippet setPathResponseFieldsDescription() { - return responseFields( - fieldWithPath("stations").description("역 목록"), - fieldWithPath("stations[].id").type(JsonFieldType.NUMBER).description("역 식별자"), - fieldWithPath("stations[].name").type(JsonFieldType.STRING).description("역 이름"), - fieldWithPath("distance").type(JsonFieldType.NUMBER).description("거리"), - fieldWithPath("duration").type(JsonFieldType.NUMBER).description("시간"), - fieldWithPath("fare").type(JsonFieldType.NUMBER).description("요금") - ); - } -} diff --git a/src/test/resources/features/station.feature b/src/test/resources/features/station.feature new file mode 100644 index 000000000..3aac79c0e --- /dev/null +++ b/src/test/resources/features/station.feature @@ -0,0 +1,21 @@ +Feature: 지하철역 관련 기능 + Scenario: 지하철역을 생성한다. + When 지하철역을 생성하면 + Then 지하철역이 생성된다 + And 지하철역 목록 조회 시 생성한 역을 찾을 수 있다 + + Scenario: 지하철역들을 생성하고 경로를 찾는다. + Given 지하철역들을 생성하고 + |name| + |교대역 | + |강남역 | + |양재역 | + |남부터미널역| + Given 지하철 노선들을 생성하고 + |name|color|upStation|downStation|distance|duration|surcharge| + |3호선 | orange | 교대역 | 남부터미널역 | 2 | 10 | 0 | + Given '3호선'에 지하철 역을 추가하고 + |upStation|downStation|distance|duration| + |남부터미널역 |양재역 |3 |10 | + When '교대역'과 '양재역' 사이 경로를 조회하면 + Then '교대역'-'남부터미널역'-'양재역' 경로가 조회된다