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 5 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
40 changes: 40 additions & 0 deletions module-api/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/

### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/

### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/

### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/

### rest docs ###
/src/main/resources/static/docs

### VS Code ###
.vscode/
Copy link

Choose a reason for hiding this comment

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

The code patch you provided seems to be a .gitignore file. It specifies the files and directories that should be ignored by Git when tracking changes in a project. Here are some observations and suggestions based on the provided patch:

  1. It's generally a good practice to include a top-level directory, such as the project root, before specifying individual files and directories to ignore. This ensures that the .gitignore rules apply correctly to the entire project.

  2. The HELP.md file is mentioned at the beginning of the patch. If this file is meant to be ignored, it should be removed from version control. Otherwise, it will continue to be tracked.

  3. The exclusion patterns using ! (e.g., !**/src/main/**/build/) are intended to override previous ignore patterns and include specific files or directories. Make sure these patterns align with your project structure and desired behavior.

  4. It's recommended to exclude generated build artifacts like build/, bin/, and out/ directories to avoid including compiled files in version control. However, be cautious when applying such exclusions to ensure necessary build configuration files are not inadvertently ignored.

  5. Integrated development environment (IDE)-specific directories and files like .gradle, .idea, and .vscode/ are excluded. This assumes that developers using different IDEs will generate their own specific configuration files without affecting the core project.

  6. The */src/main/*/out/ and */src/test/*/out/ patterns suggest excluding specific output directories from source directories that may contain generated files. This can be helpful to avoid including intermediate build results.

  7. The /src/main/resources/static/docs directory is included, implying that its contents should be tracked in version control. Ensure this matches the intended behavior; otherwise, if the documentation is dynamically generated or updated during builds, it may be better to exclude it.

Remember to periodically review and update your .gitignore file as the project evolves and new files or build artifacts are introduced. Additionally, consider collaborating with other developers on the project to fine-tune the ignore rules based on their respective development environments and tools.

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}}

|===
Loading
Loading