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

docs: API 문서화 #23

Merged
merged 4 commits into from
Feb 15, 2024
Merged
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
42 changes: 42 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ plugins {

id "org.sonarqube" version "4.4.1.3373"
id 'jacoco'

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

group = 'com.verby'
Expand All @@ -18,6 +20,7 @@ configurations {
compileOnly {
extendsFrom annotationProcessor
}
asciidoctorExt
}

repositories {
Expand All @@ -34,13 +37,52 @@ dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'io.rest-assured:rest-assured:5.4.0'
implementation 'org.springframework.boot:spring-boot-starter-mail:3.2.2'

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

test {
useJUnitPlatform()
finalizedBy 'jacocoTestReport'
}

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

test {
outputs.dir snippetsDir
}

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

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

build {
dependsOn copyDocument
}

bootJar {
dependsOn asciidoctor
from("${asciidoctor.outputDir}/html5") {
into 'static/docs'
}
}

// Jacoco
def jacocoDir = layout.buildDirectory.dir("reports/")

Expand Down
52 changes: 52 additions & 0 deletions src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
= Indp Application API Document
:doctype: book
:source-highlighter: highlightjs
:sectlinks:
:toc: left
:toclevels: 3

== 매장 (Store)

=== 매장 목록 조회 (간단 정보)

==== Request

operation::store-controller-test/find-simple-stores[snippets='http-request,query-parameters']

==== Response

operation::store-controller-test/find-simple-stores[snippets='http-response,response-fields']

=== 매장 목록 조회

==== Request

operation::store-controller-test/find-stores[snippets='http-request,query-parameters']

==== Response

operation::store-controller-test/find-stores[snippets='http-response,response-fields']

== 음악 추천 (Recommendation)

=== 음악 추천하기

==== Request

operation::recommendation-controller-test/register-recommendation[snippets='http-request,request-fields']

==== Response

operation::recommendation-controller-test/register-recommendation[snippets='http-response,response-headers']

== 문의 (Contact)

=== 문의하기

==== Request

operation::contact-controller-test/register-contact[snippets='http-request,request-fields']

==== Response

operation::contact-controller-test/register-contact[snippets='http-response,response-headers']
977 changes: 977 additions & 0 deletions src/main/resources/static/docs/index.html

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions src/test/java/com/verby/indp/config/RestDocsConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.verby.indp.config;

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 org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation;
import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler;

@TestConfiguration
public class RestDocsConfig {

@Bean
public RestDocumentationResultHandler write() {
return MockMvcRestDocumentation.document(
"{class-name}/{method-name}",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint())
);
}
}
58 changes: 58 additions & 0 deletions src/test/java/com/verby/indp/domain/BaseControllerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.verby.indp.domain;

import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.verby.indp.config.RestDocsConfig;
import com.verby.indp.domain.contact.service.ContactService;
import com.verby.indp.domain.recommendation.service.RecommendationService;
import com.verby.indp.domain.store.service.StoreService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.restdocs.RestDocumentationContextProvider;
import org.springframework.restdocs.RestDocumentationExtension;
import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.filter.CharacterEncodingFilter;

@WebMvcTest
@ExtendWith(RestDocumentationExtension.class)
@Import({RestDocsConfig.class})
public abstract class BaseControllerTest {

protected MockMvc mockMvc;

@Autowired
protected ObjectMapper objectMapper;

@Autowired
protected RestDocumentationResultHandler restDocs;

@MockBean
protected StoreService storeService;

@MockBean
protected ContactService contactService;

@MockBean
protected RecommendationService recommendationService;

@BeforeEach
void setUp(final WebApplicationContext context,
final RestDocumentationContextProvider provider) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(context)
.apply(documentationConfiguration(provider))
.alwaysDo(print())
.alwaysDo(restDocs)
.addFilters(new CharacterEncodingFilter("UTF-8", true))
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.verby.indp.domain.contact.controller;

import static com.verby.indp.domain.contact.fixture.ContactFixture.contact;
import static org.mockito.Mockito.when;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
import static org.springframework.restdocs.headers.HeaderDocumentation.responseHeaders;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post;
import static org.springframework.restdocs.payload.JsonFieldType.STRING;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.verby.indp.domain.BaseControllerTest;
import com.verby.indp.domain.contact.Contact;
import com.verby.indp.domain.contact.dto.request.RegisterContactRequest;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.test.web.servlet.ResultActions;

class ContactControllerTest extends BaseControllerTest {

@Test
@DisplayName("성공: 문의 사항을 등록한다.")
void registerContact() throws Exception {
// given
Contact contact = contact();

RegisterContactRequest request = new RegisterContactRequest(contact.getUserName(),
contact.getContent(), contact.getPhoneNumber());

when(contactService.registerContact(request)).thenReturn(contact.getContactId());

// when
ResultActions resultActions = mockMvc.perform(
post("/api/contacts")
.contentType(APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request))
);

// then
resultActions.andExpect(status().isCreated())
.andDo(
restDocs.document(
requestFields(
fieldWithPath("userName").type(STRING).description("문의자 성함"),
fieldWithPath("content").type(STRING).description("문의 내용"),
fieldWithPath("phoneNumber").type(STRING).description("문의자 연락처")
),
responseHeaders(
headerWithName("Location").description("리소스 생성 위치")
)
)
);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.verby.indp.domain.recommendation.controller;

import static com.verby.indp.domain.recommendation.fixture.RecommendationFixture.recommendation;
import static com.verby.indp.domain.store.fixture.StoreFixture.store;
import static org.mockito.Mockito.when;
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
import static org.springframework.restdocs.headers.HeaderDocumentation.responseHeaders;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post;
import static org.springframework.restdocs.payload.JsonFieldType.NUMBER;
import static org.springframework.restdocs.payload.JsonFieldType.STRING;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.verby.indp.domain.BaseControllerTest;
import com.verby.indp.domain.recommendation.Recommendation;
import com.verby.indp.domain.recommendation.dto.request.RegisterRecommendationRequest;
import com.verby.indp.domain.store.Store;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.ResultActions;

class RecommendationControllerTest extends BaseControllerTest {

@Test
@DisplayName("성공: 추천 음악 정보를 등록한다.")
void registerRecommendation() throws Exception {
// given
Store store = store();
Recommendation recommendation = recommendation(store());

RegisterRecommendationRequest request = new RegisterRecommendationRequest(store.getStoreId(),
recommendation.getInformation(), recommendation.getPhoneNumber());

when(recommendationService.registerRecommendation(request)).thenReturn(recommendation.getRecommendationId());

// when
ResultActions resultActions = mockMvc.perform(
post("/api/music/recommendations")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request))
);

// then
resultActions.andExpect(status().isCreated()).
andDo(
restDocs.document(
requestFields(
fieldWithPath("storeId").type(NUMBER).description("매장 ID"),
fieldWithPath("information").type(STRING).description("추천 음악 정보"),
fieldWithPath("phoneNumber").type(STRING).description("추천인 연락처")
),
responseHeaders(
headerWithName("Location").description("리소스 생성 위치")
)
)
);
}

}
Loading
Loading