Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

8주차 : REST DOCS #78

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# 어떤 이미지로부터 만들지 선택합니다. 이 이미지는 우리 프로젝트를 빌드를 하기 위해서만 사용되는 이미지입니다. 빌드 하기 위해서 모든 파일을 컨테이너로 복사한 후 빌드를 실행합니다. 빌드를 하는 용도로만 사용되고 더 이상 사용되지 않습니다.
FROM openjdk:11 AS builder
# 현재 폴더에서 컨테이너의 현재 폴더로 복사합니다.
COPY . .
# 명령어를 실행합니다.
RUN sed -i 's/\r//' ./gradlew
RUN ["./gradlew", "assemble"]
# 이 이미지가 우리가 실제로 실행시킬 이미지입니다. 마찬가지로 openjdk로 이미지를 사용합니다.
FROM openjdk:11
# 위의 빌드 이미지로부터 만들어진 jar 파일을 복사합니다.
COPY --from=builder /app/build/libs/app.jar .
# 서버를 실행시키는 명령어입니다.
CMD ["java", "-jar", "app.jar"]
40 changes: 39 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,25 @@
* For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle
* User Manual available at https://docs.gradle.org/6.7/userguide/building_java_projects.html
*/

buildscript {
ext {
asciidocVersion = "2.0.6.RELEASE"
}
}
plugins {
// Apply the application plugin to add support for building a CLI application in Java.
id 'application'

// Spring
id 'org.springframework.boot' version '2.3.5.RELEASE'
id 'io.spring.dependency-management' version '1.0.10.RELEASE'

// asciidoctor
id "org.asciidoctor.jvm.convert" version "3.3.2"
}

ext {
snippetsDir = file('build/generated-snippets')
}

sourceCompatibility = '1.8'
Expand All @@ -22,6 +33,10 @@ configurations {
runtimeClasspath {
extendsFrom developmentOnly
}
compileOnly {
extendsFrom annotationProcessor
}
asciidoctorExt
}

repositories {
Expand Down Expand Up @@ -78,6 +93,12 @@ dependencies {
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}

//RES DOC Lib
asciidoctorExt "org.springframework.restdocs:spring-restdocs-asciidoctor:${asciidocVersion}"
testImplementation "org.springframework.restdocs:spring-restdocs-mockmvc:${asciidocVersion}"

implementation 'org.mariadb.jdbc:mariadb-java-client'
}

application {
Expand All @@ -89,3 +110,20 @@ tasks.named('test') {
// Use junit platform for unit tests.
useJUnitPlatform()
}
test {
outputs.dir snippetsDir
}

asciidoctor {
inputs.dir snippetsDir
configurations 'asciidoctorExt'
dependsOn test
}

bootJar {
dependsOn asciidoctor
copy {// 생성된 html 파일 복사
from asciidoctor.outputDir
into "src/main/resources/static/docs"
}
}
80 changes: 72 additions & 8 deletions app/src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
@@ -1,17 +1,81 @@
= 고양이 장난감 가게 API
:toc: left
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toclevels: 1
:sectlinks:

== GET /products
== 상품 단건 조회
상품에 대한 자세한 정보를 JSON 형태로 돌려준다.

상품 목록을 JSON 형태로 돌려준다.
=== 요청
include::{snippets}/product-inquiry/http-request.adoc[]

include::{snippets}/get-products/http-request.adoc[]
include::{snippets}/product-inquiry/path-parameters.adoc[]

include::{snippets}/get-products/http-response.adoc[]
=== 응답
include::{snippets}/product-inquiry/http-response.adoc[]

== GET /product/{id}
include::{snippets}/product-inquiry/response-fields.adoc[]

상품에 대한 자세한 정보를 JSON 형태로 돌려준다.
=== curl
include::{snippets}/product-inquiry/curl-request.adoc[]

== 상품 목록 조회
상품 전체 목록에 대한 정보를 JSON 형태로 돌려준다.

=== 요청
include::{snippets}/product-inquiry-all/http-request.adoc[]

=== 응답
include::{snippets}/product-inquiry-all/http-response.adoc[]

include::{snippets}/product-inquiry-all/response-fields.adoc[]

=== curl
include::{snippets}/product-inquiry-all/curl-request.adoc[]

== 상품 생성
상품 생성 후 생성한 상품에 대한 정보를 JSON 형태로 돌려준다.

=== 요청
include::{snippets}/product-create/http-request.adoc[]

=== 응답
include::{snippets}/product-create/http-response.adoc[]

include::{snippets}/product-create/response-fields.adoc[]

=== curl
include::{snippets}/product-create/curl-request.adoc[]

== 상품 수정
상품 수정 후 생성한 상품에 대한 정보를 JSON 형태로 돌려준다.

=== 요청
include::{snippets}/product-update/http-request.adoc[]

include::{snippets}/product-update/path-parameters.adoc[]

=== 응답
include::{snippets}/product-update/http-response.adoc[]

include::{snippets}/product-update/response-fields.adoc[]

=== curl
include::{snippets}/product-update/curl-request.adoc[]

== 상품 삭제
상품을 삭제할 아이디를 받으면 상품을 삭제한다.

=== 요청
include::{snippets}/product-delete/http-request.adoc[]

include::{snippets}/product-delete/path-parameters.adoc[]

include::{snippets}/get-product/http-request.adoc[]
=== 응답
include::{snippets}/product-delete/http-response.adoc[]

include::{snippets}/get-product/http-response.adoc[]
=== curl
include::{snippets}/product-delete/curl-request.adoc[]
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
@NoArgsConstructor
@AllArgsConstructor
public class ProductData {
private Long id;

@NotBlank
@Mapping("name")
Expand Down
4 changes: 3 additions & 1 deletion app/src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
spring:
datasource:
url: jdbc:h2:~/data/demo
url: jdbc:mariadb://mariadb:3306/test
username: root
password: root1234
jpa:
hibernate:
ddl-auto: update
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package com.codesoom.assignment.controllers;

import com.codesoom.assignment.dto.ProductData;
import com.codesoom.assignment.utils.RestDocsSupporter;
import com.google.common.net.HttpHeaders;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import org.springframework.test.web.servlet.ResultActions;


import java.util.Arrays;

import static com.codesoom.assignment.utils.TestHelper.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.BDDMockito.given;
import static org.springframework.http.MediaType.*;
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*;
import static org.springframework.restdocs.payload.PayloadDocumentation.*;
import static org.springframework.restdocs.request.RequestDocumentation.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@DisplayName("ProductController 테스트")
@SuppressWarnings("NonAsciiCharacters")
public class ProductControllerDocTest extends RestDocsSupporter {

@Test
@DisplayName("상품 전체 조회 테스트")
void RESTDOC_상품_전체_조회() throws Exception {
given(productService.getProducts())
.willReturn(Arrays.asList(TEST_PRODUCT));

ResultActions result = mockMvc.perform(get("/products")
.accept(APPLICATION_JSON));

result.andExpect(status().isOk())
.andDo(
document("product-inquiry-all",
responseFields(
fieldWithPath("[].id").description("상품 아이디"),
fieldWithPath("[].name").description("상품명"),
fieldWithPath("[].price").description("가격"),
fieldWithPath("[].maker").description("제조사"),
fieldWithPath("[].imageUrl").description("이미지 URL")
)
));
}

@Test
@DisplayName("상품 단건 조회 테스트")
void RESTDOC_상품_단건_조회() throws Exception {
given(productService.getProduct(anyLong()))
.willReturn(TEST_PRODUCT);

ResultActions result = mockMvc.perform(get("/products/{productId}", 1L)
.accept(APPLICATION_JSON));

result.andExpect(status().isOk())
.andDo(document("product-inquiry", pathParameters(
parameterWithName("productId").description("조회할 상품 아이디")
),
responseFields(
fieldWithPath("id").description("상품 아이디"),
fieldWithPath("name").description("상품명"),
fieldWithPath("price").description("가격"),
fieldWithPath("maker").description("제조사"),
fieldWithPath("imageUrl").description("이미지 URL")
)
));
}

@Test
@DisplayName("상품 생성 테스트")
void RESTDOC_상품_생성() throws Exception {
given(productService.createProduct(any(ProductData.class)))
.willReturn(TEST_PRODUCT);

ResultActions result = mockMvc.perform(post("/products")
.accept(APPLICATION_JSON)
.contentType(APPLICATION_JSON)
.content(objectMapper.writeValueAsString(TEST_PRODUCT_DATA))
.header(HttpHeaders.AUTHORIZATION, "Bearer " + VALID_TOKEN));


result.andExpect(status().isCreated())
.andDo(document("product-create",
requestHeaders(
headerWithName(HttpHeaders.AUTHORIZATION).description("회원 인증 토큰")
),
requestFields(
fieldWithPath("name").description("상품명"),
fieldWithPath("price").description("가격"),
fieldWithPath("maker").description("제조사"),
fieldWithPath("imageUrl").description("이미지 URL")
),
responseFields(
fieldWithPath("id").description("상품 아이디"),
fieldWithPath("name").description("상품명"),
fieldWithPath("price").description("가격"),
fieldWithPath("maker").description("제조사"),
fieldWithPath("imageUrl").description("이미지 URL")
)
));
}


@Test
@DisplayName("상품 수정 테스트")
void RESTDOC_상품_수정() throws Exception {
given(productService.updateProduct(anyLong(), any(ProductData.class)))
.willReturn(UPDATED_PRODUCT);

ResultActions result = mockMvc.perform(patch("/products/{productId}", 1L)
.accept(APPLICATION_JSON)
.contentType(APPLICATION_JSON)
.header(HttpHeaders.AUTHORIZATION, "Bearer " + VALID_TOKEN)
.content(objectMapper.writeValueAsString(UPDATE_PRODUCT_DATA)));

result.andExpect(status().isOk())
.andDo(document("product-update",
requestHeaders(
headerWithName(HttpHeaders.AUTHORIZATION).description("회원 인증 토큰")
),
pathParameters(
parameterWithName("productId").description("수정 할 상품 아이디")
),
requestFields(
fieldWithPath("name").description("상품명"),
fieldWithPath("price").description("가격"),
fieldWithPath("maker").description("제조사"),
fieldWithPath("imageUrl").description("이미지 URL")
),
responseFields(
fieldWithPath("id").description("상품 아이디"),
fieldWithPath("name").description("상품명"),
fieldWithPath("price").description("가격"),
fieldWithPath("maker").description("제조사"),
fieldWithPath("imageUrl").description("이미지 URL")
)
));
}

@Test
void RESTDOC_상품_삭제() throws Exception {
given(productService.deleteProduct(anyLong()))
.willReturn(TEST_PRODUCT);

ResultActions result = mockMvc.perform(delete("/products/{productId}", 1L)
.accept(APPLICATION_JSON)
.header(HttpHeaders.AUTHORIZATION, "Bearer " + VALID_TOKEN));

result.andExpect(status().isOk())
.andDo(document("product-delete",
requestHeaders(
headerWithName(HttpHeaders.AUTHORIZATION).description("회원 인증 토큰")
),
pathParameters(
parameterWithName("productId").description("삭제 할 상품 아이디")
)
));
}
}
Loading
Loading