Skip to content

Commit

Permalink
✨ RestDocs 추가 및 docstest 작성
Browse files Browse the repository at this point in the history
  • Loading branch information
miiiinju1 committed May 1, 2024
1 parent 52a52e1 commit 340b587
Show file tree
Hide file tree
Showing 11 changed files with 451 additions and 0 deletions.
31 changes: 31 additions & 0 deletions 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.2'
id 'io.spring.dependency-management' version '1.1.4'
id 'org.asciidoctor.jvm.convert' version '3.3.2'
}
plugins {
id "org.sonarqube" version "4.4.1.3373"
Expand Down Expand Up @@ -65,6 +66,8 @@ configurations {
compileOnly {
extendsFrom annotationProcessor
}

asciidoctorExt
}

repositories {
Expand Down Expand Up @@ -108,6 +111,10 @@ dependencies {
// Test Dependencies
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'com.h2database:h2'

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

tasks.named('sonarqube').configure {
Expand All @@ -119,7 +126,31 @@ tasks.named('test') {
finalizedBy 'jacocoTestReport'
}

ext { //전역 변수
snippetsDir = file("build/generated-snippets")
}

test {
outputs.dir snippetsDir
}

asciidoctor {
inputs.dir snippetsDir
configurations 'asciidoctorExt'

sources { // 특정 파일만 html로 만든다
include '**/index.adoc'
}
baseDirFollowsSourceFile() // 다른 adoc 파일을 참조할 때 경로를 baseDir 기준으로 찾음
dependsOn test
}

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

jacocoTestReport {
dependsOn test
Expand Down
19 changes: 19 additions & 0 deletions src/docs/asciidoc/api/dailydefense/dailydefense.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[[Daily-Defense]]
== 오늘의 문제 정보 조회

==== Request
include::{snippets}/daily-defense-info/http-request.adoc[]

==== Response
include::{snippets}/daily-defense-info/http-response.adoc[]
include::{snippets}/daily-defense-info/response-fields.adoc[]


== 오늘의 문제 시작

==== Request
include::{snippets}/daily-defense-start/http-request.adoc[]

==== Response
include::{snippets}/daily-defense-start/http-response.adoc[]
include::{snippets}/daily-defense-start/response-fields.adoc[]
16 changes: 16 additions & 0 deletions src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
ifndef::snippets[]
:snippets: ../../build/generated-snippets
endif::[]
= 모두의 랜덤 디펜스 REST API
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: left
:toclevels: 1
:sectlinks:

include::api/dailydefense/dailydefense.adoc[]



[[Daily-Defense-List]]
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package kr.co.morandi.backend.defense_management.application.response.session;

import com.fasterxml.jackson.annotation.JsonProperty;
import kr.co.morandi.backend.problem_information.application.response.problemcontent.ProblemContent;
import kr.co.morandi.backend.defense_management.application.response.tempcode.TempCodeResponse;
import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language;
Expand All @@ -22,6 +23,10 @@ public class DefenseProblemResponse {
private Language lastAccessLanguage;
private Set<TempCodeResponse> tempCodes;

public boolean getIsCorrect() {
return isCorrect;
}

@Builder
private DefenseProblemResponse(Long problemId, Long problemNumber, Long baekjoonProblemId,
ProblemContent content, boolean isCorrect, Language lastAccessLanguage,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package kr.co.morandi.backend.defense_management.application.response.session;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType;
import lombok.AccessLevel;
import lombok.Builder;
Expand All @@ -16,6 +19,7 @@ public class StartDailyDefenseResponse {
private Long defenseSessionId;
private String contentName;
private DefenseType defenseType;
// @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime lastAccessTime;
private List<DefenseProblemResponse> defenseProblems;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package kr.co.morandi.backend.defense_management.infrastructure.controller;

import org.junit.jupiter.api.extension.ExtendWith;

import static org.junit.jupiter.api.Assertions.*;

class DefenseMangementControllerTest {


}
33 changes: 33 additions & 0 deletions src/test/java/kr/co/morandi/backend/docs/RestDocsSupport.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package kr.co.morandi.backend.docs;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
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.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;

@ExtendWith(RestDocumentationExtension.class)
public abstract class RestDocsSupport {

protected MockMvc mockMvc;
protected ObjectMapper objectMapper = new ObjectMapper()
.registerModule(new JavaTimeModule())
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

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

protected abstract Object initController();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package kr.co.morandi.backend.docs.dailydefense;

import kr.co.morandi.backend.defense_information.application.dto.response.DailyDefenseInfoResponse;
import kr.co.morandi.backend.defense_information.application.dto.response.DailyDefenseProblemInfoResponse;
import kr.co.morandi.backend.defense_information.application.port.in.DailyDefenseUseCase;
import kr.co.morandi.backend.defense_information.infrastructure.controller.DailyDefenseController;
import kr.co.morandi.backend.docs.RestDocsSupport;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.restdocs.payload.JsonFieldType;
import org.springframework.test.web.servlet.ResultActions;

import java.util.List;

import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.S5;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.*;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

public class DailyDefenseControllerDocsTest extends RestDocsSupport {

private final DailyDefenseUseCase dailyDefenseUseCase = mock(DailyDefenseUseCase.class);
@Override
protected Object initController() {
return new DailyDefenseController(dailyDefenseUseCase);
}

@DisplayName("DailyDefense 정보를 가져오는 API")
@Test
void getDailyDefenseInfo() throws Exception {

final DailyDefenseProblemInfoResponse problem = DailyDefenseProblemInfoResponse.builder()
.problemNumber(1L)
.problemId(1L)
.baekjoonProblemId(1000L)
.difficulty(S5)
.solvedCount(1L)
.submitCount(1L)
.isSolved(true)
.build();

when(dailyDefenseUseCase.getDailyDefenseInfo(any(), any()))
.thenReturn(DailyDefenseInfoResponse.builder()
.problems(List.of(problem))
.defenseName("test")
.attemptCount(1L)
.problemCount(5)
.build());

final ResultActions perform = mockMvc.perform(get("/daily-defense"));

perform
.andExpect(status().isOk())
.andExpect(jsonPath("$.problems").isArray())
.andExpect(jsonPath("$.defenseName").isString())
.andExpect(jsonPath("$.attemptCount").isNumber())
.andExpect(jsonPath("$.problemCount").isNumber())
.andDo(document("daily-defense-info",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
responseFields(
fieldWithPath("defenseName").type(JsonFieldType.STRING)
.description("디펜스 이름"),
fieldWithPath("problemCount").type(JsonFieldType.NUMBER)
.description("총 문제 수"),
fieldWithPath("attemptCount").type(JsonFieldType.NUMBER)
.description("디펜스 시도 횟수"),
fieldWithPath("problems").type(JsonFieldType.ARRAY)
.description("디펜스 문제 목록"),
fieldWithPath("problems[].problemNumber").type(JsonFieldType.NUMBER)
.description("시도하는 문제 번호"),
fieldWithPath("problems[].problemId").type(JsonFieldType.NUMBER)
.description("시도하는 문제의 PK"),
fieldWithPath("problems[].baekjoonProblemId").type(JsonFieldType.NUMBER)
.description("백준 문제 번호"),
fieldWithPath("problems[].difficulty").type(JsonFieldType.STRING)
.description("문제 난이도 ex) SILVER"),
fieldWithPath("problems[].solvedCount").type(JsonFieldType.NUMBER)
.description("정답자 수"),
fieldWithPath("problems[].submitCount").type(JsonFieldType.NUMBER)
.description("제출한 사람 수"),
fieldWithPath("problems[].isSolved").type(JsonFieldType.BOOLEAN)
.optional()
.description("해당 사용자가 정답을 맞췄는지 여부, 이 필드가 없으면 아직 시도하지 않은 문제")
)
));

}
}
Loading

0 comments on commit 340b587

Please sign in to comment.