Skip to content

Commit

Permalink
Merge pull request #85 from woowacourse-teams/chore-apiDocsSetup
Browse files Browse the repository at this point in the history
[BE] chore: API 문서 자동화 설정 및 BASE 코드 작성
  • Loading branch information
jimi567 authored Jul 17, 2024
2 parents 621cc1f + d84463f commit 3183f16
Show file tree
Hide file tree
Showing 4 changed files with 207 additions and 10 deletions.
67 changes: 57 additions & 10 deletions backend/build.gradle
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
buildscript {
ext {
restdocsApiSpecVersion = '0.17.1'
}
}

plugins {
id 'java'
id 'org.springframework.boot' version '3.3.1'
id 'io.spring.dependency-management' version '1.1.5'
id 'org.asciidoctor.jvm.convert' version '3.3.2'
id 'com.epages.restdocs-api-spec' version "${restdocsApiSpecVersion}"
id 'org.hidetake.swagger.generator' version '2.18.2'
}

group = 'com.woowacourse'
Expand All @@ -20,12 +27,25 @@ configurations {
}
}

repositories {
mavenCentral()
swaggerSources {
sample {
setInputFile(file("${project.buildDir}/api-spec/openapi3.yaml"))
}
}

ext {
set('snippetsDir', file("build/generated-snippets"))
openapi3 {
servers = [
{ url = "http://3.35.5.179/" },
{ url = "http://localhost:8080" }
]
title = "반갑개 API 명세서"
description = "반갑개 명세서"
version = "0.0.1"
format = "yaml"
}

repositories {
mavenCentral()
}

dependencies {
Expand All @@ -40,16 +60,43 @@ dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
testImplementation 'io.rest-assured:rest-assured:5.4.0'
testImplementation 'com.epages:restdocs-api-spec-mockmvc:' + restdocsApiSpecVersion

testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

swaggerUI 'org.webjars:swagger-ui:4.11.1'
}

tasks.named('test') {
outputs.dir snippetsDir
tasks.withType(Test) {
useJUnitPlatform()
systemProperty 'file.encoding', 'UTF-8'
}

tasks.withType(GenerateSwaggerUI) {
dependsOn 'openapi3'
}

// 생성된 SwaggerUI 를 jar 에 포함시키기 위해 build/resources 경로로 로 복사
tasks.register('copySwaggerUI', Copy) {
dependsOn 'generateSwaggerUISample'

def generateSwaggerUISampleTask = tasks.named('generateSwaggerUISample', GenerateSwaggerUI).get()

from("${generateSwaggerUISampleTask.outputDir}")
into("${project.buildDir}/resources/main/static/docs")
}

// 3.4.3
// bootJar 실행 전, copySwaggerUI 를 실행하도록 설정
bootJar {
dependsOn 'copySwaggerUI'
}


jar {
from tasks.copySwaggerUI
}

tasks.named('asciidoctor') {
inputs.dir snippetsDir
dependsOn test
resolveMainClassName {
dependsOn 'copySwaggerUI'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.woowacourse.friendogly.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost")
.allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE")
.allowedHeaders("*")
.allowCredentials(true);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.woowacourse.friendogly.docs;

import static com.epages.restdocs.apispec.ResourceDocumentation.resource;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper;
import com.epages.restdocs.apispec.ResourceSnippetParameters;
import com.epages.restdocs.apispec.Schema;
import com.woowacourse.friendogly.member.controller.MemberController;
import com.woowacourse.friendogly.member.dto.request.SaveMemberRequest;
import com.woowacourse.friendogly.member.dto.response.saveMemberResponse;
import com.woowacourse.friendogly.member.service.MemberCommandService;
import com.woowacourse.friendogly.member.service.MemberQueryService;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
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.restdocs.mockmvc.RestDocumentationRequestBuilders;
import org.springframework.restdocs.payload.JsonFieldType;

@WebMvcTest(MemberController.class)
public class MemberApiDocsTest extends RestDocsTest {

@MockBean
MemberCommandService memberCommandService;
@MockBean
MemberQueryService memberQueryService;

@DisplayName("회원 생성 문서화 테스트 예시")
@Test
void saveMember_Success() throws Exception {
SaveMemberRequest request = new SaveMemberRequest("반갑개");
saveMemberResponse response = new saveMemberResponse(1L);

Mockito.when(memberCommandService.saveMember(request))
.thenReturn(response);
// http method() static import 가능, 단 라이브러리 확인 필수
mockMvc.perform(RestDocumentationRequestBuilders.post("/members")
.content(objectMapper.writeValueAsString(request))
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isCreated())
//*** document() static import 가능함, 단 라이브러리 확인 필수
.andDo(MockMvcRestDocumentationWrapper.document("member-save-201",
getDocumentRequest(),
getDocumentResponse(),
resource(ResourceSnippetParameters.builder()
.tag("Member API")
.summary("회원 생성 API")
.requestFields(
fieldWithPath("name").type(JsonFieldType.STRING).description("회원 이름"))
.requestSchema(Schema.schema("saveMemberRequest"))
.responseSchema(Schema.schema("응답DTO 이름"))
.build()))
);
}

@Override
protected Object controller() {
return new MemberController(memberCommandService);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.woowacourse.friendogly.docs;

import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.modifyUris;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.restdocs.RestDocumentationContextProvider;
import org.springframework.restdocs.RestDocumentationExtension;
import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor;
import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

@ExtendWith(RestDocumentationExtension.class)
abstract class RestDocsTest {
protected static ObjectMapper objectMapper;

protected MockMvc mockMvc;

@BeforeAll
static void beforeAll() {
objectMapper = new ObjectMapper();
JavaTimeModule module = new JavaTimeModule();
module.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ISO_LOCAL_TIME));
module.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ISO_LOCAL_DATE));
module.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ISO_LOCAL_DATE));
objectMapper.registerModule(module);
}

@BeforeEach
void setUp(RestDocumentationContextProvider provider) {
this.mockMvc = MockMvcBuilders.standaloneSetup(controller())
.apply(documentationConfiguration(provider))
.setMessageConverters(new MappingJackson2HttpMessageConverter(objectMapper))
.build();
}

protected OperationRequestPreprocessor getDocumentRequest() {
return preprocessRequest(
modifyUris()
.scheme("https")
.host("docs.api.com")
.removePort(),
prettyPrint());
}

protected OperationResponsePreprocessor getDocumentResponse() {
return preprocessResponse(prettyPrint());
}

protected abstract Object controller();
}

0 comments on commit 3183f16

Please sign in to comment.