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

[feat] Rest Docs 적용 #73

Merged
merged 7 commits into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
39 changes: 38 additions & 1 deletion module-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ plugins {
id 'java'
id 'org.springframework.boot' version '3.2.0'
id 'io.spring.dependency-management' version '1.1.4'
id "org.asciidoctor.jvm.convert" version "3.3.2"
}

group = 'com.kernel360'
Expand All @@ -11,7 +12,12 @@ java {
sourceCompatibility = '17'
}

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

configurations {
asciidoctorExt
compileOnly {
extendsFrom annotationProcessor
}
Expand All @@ -30,32 +36,63 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-aop'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
//implementation 'org.springframework.boot:spring-boot-starter-security'

// jasypt
implementation 'com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.5'

// flyway
implementation 'org.flywaydb:flyway-core'

// postgresql
runtimeOnly 'org.postgresql:postgresql'

//lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'

// validataion
implementation 'org.springframework.boot:spring-boot-starter-validation:2.7.4'

// test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'

// test - fixture Monkey
testImplementation 'net.jqwik:jqwik:1.7.3'
testImplementation("com.navercorp.fixturemonkey:fixture-monkey-starter:1.0.0")
testImplementation("com.navercorp.fixturemonkey:fixture-monkey-jakarta-validation:1.0.0")

// rest docs
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor'
}

tasks.named('test') {
outputs.dir snippetsDir
useJUnitPlatform()
}

tasks.register("prepareKotlinBuildScriptModel") {}
asciidoctor {
dependsOn test
configurations 'asciidoctorExt'
baseDirFollowsSourceFile()
inputs.dir snippetsDir
}

asciidoctor.doFirst {
delete file('src/main/resources/static/docs')
}

task copyDocument(type: Copy) {
dependsOn asciidoctor
from file("build/docs/asciidoc")
into file("src/main/resources/static/docs")
}

bootJar {
dependsOn copyDocument
}

tasks.register("prepareKotlinBuildScriptModel") {}
Copy link

Choose a reason for hiding this comment

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

Here are some findings and suggestions for the code patch:

  1. Dependency Upgrade: Consider upgrading the dependencies to their latest versions, as newer versions often include bug fixes and improvements.

  2. Configuration Block: Move the ext block inside the java block for better organization.

  3. Output Directory: Add the output directory for Asciidoctor generated snippets (snippetsDir) as an input for the asciidoctor task to track changes properly.

  4. Configuration Name: Change asciidoctorExt to a more descriptive name, like restdocsAsciidoctor, as it is used for Spring REST Docs.

  5. Ensure Clean Slate: Delete the existing src/main/resources/static/docs directory before generating new documentation to avoid any stale files remaining.

  6. Documentation Copy: Create a task named copyDocument that depends on the asciidoctor task. This task copies the generated AsciiDoc files from build/docs/asciidoc to src/main/resources/static/docs.

  7. Jar Creation: Configure the bootJar task to depend on the copyDocument task so that the generated documentation is included in the final JAR file.

Overall, these changes improve the build process and ensure that the latest documentation is included in the application's distribution.

11 changes: 11 additions & 0 deletions module-api/src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
= API Document
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: left
:toclevels: 2
:sectlinks:

include::overview.adoc[]
include::sample-api.adoc[]
// !!! 위 라인 1줄은 추후 삭제 예정입니다 !!!
14 changes: 14 additions & 0 deletions module-api/src/docs/asciidoc/overview.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[[overview]]
== Overview

[[overview-host]]
=== Domain

[cols="3,7"]
|===
| 환경 | Domain
| 개발 서버
| `http://washpedia.my-project.life`
| 운영 서버
|
|===
23 changes: 23 additions & 0 deletions module-api/src/docs/asciidoc/sample-api.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// !!! 삭제 예정 파일입니다 !!!
== 샘플 API

// [[]] 안에는 a 태그 이름 들어갑니다 (http://localhost:8080/docs/index#공통코드-조회)
[[공통코드-조회]]
=== 공통코드 조회

// [Request] 실제로 API 에서 필요한 내용들만 아래 내용을 추가합니다
==== Request
include::{snippets}/commoncode/get-common-codes/path-parameters.adoc[]
// include::{snippets}/commoncode/get-common-codes/query-parameters.adoc[]
// include::{snippets}/commoncode/get-common-codes/request-fields.adoc[]

===== HTTP Request 예시
include::{snippets}/commoncode/get-common-codes/http-request.adoc[]

// [Response] 실제로 API 에서 필요한 내용들만 아래 내용을 추가합니다
==== Response
// include::{snippets}/commoncode/get-common-codes/response-fields.adoc[]
include::{snippets}/commoncode/get-common-codes/response-fields-value.adoc[]

===== HTTP Response 예시
include::{snippets}/commoncode/get-common-codes/http-response.adoc[]
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@

import com.kernel360.commoncode.service.CommonCodeService;
import com.kernel360.commoncode.dto.CommonCodeDto;
import com.kernel360.response.ApiResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

import static com.kernel360.commoncode.code.CommonCodeBusinessCode.GET_COMMON_CODE_SUCCESS;

@RestController
@RequestMapping("/commoncode")
public class CommonCodeController {
Expand All @@ -26,4 +31,13 @@ public List<CommonCodeDto> getCommonCode (@PathVariable String codeName){

return commonCodeService.getCodes(codeName);
}

// !!! 아래 메서드는 추후 삭제 예정입니다 !!!
@GetMapping("/test/{codeName}")
public ResponseEntity<ApiResponse> getCommonCode_1 (@PathVariable String codeName){
List<CommonCodeDto> codes = commonCodeService.getCodes(codeName);
ApiResponse<List<CommonCodeDto>> response = ApiResponse.of(GET_COMMON_CODE_SUCCESS, codes);

return new ResponseEntity<>(response, HttpStatus.OK);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.kernel360.common;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.kernel360.commoncode.controller.CommonCodeController;
import com.kernel360.commoncode.service.CommonCodeService;
import com.kernel360.member.controller.MemberController;
import com.kernel360.member.service.MemberService;
import com.kernel360.product.controller.ProductController;
import com.kernel360.product.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;

@WebMvcTest({
CommonCodeController.class,
MemberController.class,
ProductController.class
})
@AutoConfigureRestDocs
public abstract class ControllerTest {

@Autowired
protected MockMvc mockMvc;

@Autowired
protected ObjectMapper objectMapper;

@MockBean
protected CommonCodeService commonCodeService;

@MockBean
protected MemberService memberService;

@MockBean
protected ProductService productService;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.kernel360.common.utils;

import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor;
import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor;

import static org.springframework.restdocs.operation.preprocess.Preprocessors.*;

public interface RestDocumentUtils {

static OperationRequestPreprocessor getDocumentRequest() {
return preprocessRequest(modifyUris().scheme("http")
.host("washpedia.my-project.life")
.removePort(), prettyPrint());
}

static OperationResponsePreprocessor getDocumentResponse() {
return preprocessResponse(prettyPrint());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package com.kernel360.commoncode.controller;

import com.kernel360.common.ControllerTest;
import com.kernel360.commoncode.dto.CommonCodeDto;
import org.junit.jupiter.api.Test;
import org.springframework.restdocs.payload.JsonFieldType;
import org.springframework.test.web.servlet.ResultActions;

import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;

import static com.kernel360.common.utils.RestDocumentUtils.getDocumentRequest;
import static com.kernel360.common.utils.RestDocumentUtils.getDocumentResponse;
import static org.mockito.BDDMockito.given;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.payload.PayloadDocumentation.*;
import static org.springframework.restdocs.request.RequestDocumentation.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

// !!! 삭제 예정 파일입니다 !!!
class CommonCodeControllerRestDocsTest extends ControllerTest {

@Test
void commmonCodeSearch() throws Exception {
//given
List<CommonCodeDto> responseList = Arrays.asList(
CommonCodeDto.of(
11L,
"Sedan",
10,
"cartype",
1,
true,
"세단",
LocalDate.now(),
"admin",
null,
null),
CommonCodeDto.of(
12L,
"Hatchback",
10,
"cartype",
2,
true,
"해치백",
LocalDate.now(),
"admin",
null,
null)
);

String pathVariable = "color";
given(commonCodeService.getCodes(pathVariable)).willReturn(responseList);


//when
ResultActions result = this.mockMvc.perform(
get("/commoncode/test/{codeName}", pathVariable));


//then
//pathParameters, pathParameters, requestFields, responseFields는 필요 시 각각 작성
result.andExpect(status().isOk())
.andDo(document(
"commoncode/get-common-codes",
getDocumentRequest(),
getDocumentResponse(),
pathParameters(
parameterWithName("codeName").description("코드명")
),
// pathParameters(
// parameterWithName("size").description("size").optional(),
// parameterWithName("page").description("page").optional()
// ),
// requestFields(
// fieldWithPath("codeName").type(JsonFieldType.STRING).description("코드명"),
// fieldWithPath("upperName").type(JsonFieldType.STRING).description("상위 코드명").optional()
// ),
// responseFields(
// fieldWithPath("codeNo").type(JsonFieldType.NUMBER).description("코드번호"),
// )
responseFields(beneathPath("value").withSubsectionId("value"),
fieldWithPath("codeNo").type(JsonFieldType.NUMBER).description("코드번호"),
fieldWithPath("codeName").type(JsonFieldType.STRING).description("코드명"),
fieldWithPath("upperNo").type(JsonFieldType.NUMBER).description("상위 코드번호").optional(),
fieldWithPath("upperName").type(JsonFieldType.STRING).description("상위 코드명").optional(),
fieldWithPath("sortOrder").type(JsonFieldType.NUMBER).description("정렬순서"),
fieldWithPath("isUsed").type(JsonFieldType.BOOLEAN).description("사용여부"),
fieldWithPath("description").type(JsonFieldType.STRING).description("설명"),
fieldWithPath("createdAt").type(JsonFieldType.STRING).description("생성일시"),
fieldWithPath("createdBy").type(JsonFieldType.STRING).description("생성자"),
fieldWithPath("modifiedAt").type(JsonFieldType.STRING).description("수정일시").optional(),
fieldWithPath("modifiedBy").type(JsonFieldType.STRING).description("수정자").optional()
)
));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
===== End Point
{{path}}

===== Path Parameters
|===

|파라미터|설명

{{#parameters}}
|{{#tableCellContent}}`+{{name}}+`{{/tableCellContent}}
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
{{/parameters}}

|===
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
===== Query Parameters
|===

|파라미터|필수값|설명

{{#parameters}}
|{{#tableCellContent}}`+{{name}}+`{{/tableCellContent}}
|{{#tableCellContent}}{{^optional}}true{{/optional}}{{/tableCellContent}}
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
{{/parameters}}

|===
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
===== Request Fields
|===

|필드명|타입|필수값|설명

{{#fields}}
|{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}}
|{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}}
|{{#tableCellContent}}{{^optional}}true{{/optional}}{{/tableCellContent}}
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
{{/fields}}

|===
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
===== Response Fields
|===

|필드명|타입|설명

{{#fields}}
|{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}}
|{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}}
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
{{/fields}}

|===
Loading