diff --git a/build.gradle b/build.gradle index da538b0..1cfe7ff 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,9 @@ plugins { id 'java' + id 'jacoco' id 'org.springframework.boot' version '2.7.14-SNAPSHOT' id 'io.spring.dependency-management' version '1.0.15.RELEASE' + id "org.asciidoctor.jvm.convert" version "3.3.2" } group = 'com.aiary' @@ -11,9 +13,55 @@ java { sourceCompatibility = '11' } -configurations { - compileOnly { - extendsFrom annotationProcessor +jacoco { + toolVersion = '0.8.5' +} + +// 테스트 태스크에 Jacoco 통합 +test{ + finalizedBy 'jacocoTestReport' +} + +// 테스트 작업이 완료된 후 'jacocoTestReport' 작업을 실행하도록 설정 +jacocoTestReport { + reports { + xml.required = false + csv.required = false + html.required = true + } + finalizedBy jacocoTestCoverageVerification + afterEvaluate { + classDirectories.setFrom(files(classDirectories.files.collect { + fileTree(dir: it, exclude: [ + '**/dto/**', + "**/*Application*", + "**/global/**", + "**/config/**", + "**/exceptions/**" + ]) + })) + } +} + +// Jacoco 코드 커버리지 검증 설정 +jacocoTestCoverageVerification { + violationRules { + rule { + enabled = true + element = 'CLASS' + + limit { + counter = 'METHOD' + value = 'COVEREDRATIO' + minimum = 0.00 + } + + limit { + counter = 'INSTRUCTION' // 조건, 반복문 + value = 'COVEREDRATIO' + minimum = 0.00 + } + } } } @@ -40,10 +88,42 @@ dependencies { compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' + + // test testImplementation 'org.springframework.security:spring-security-test' testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.mockito:mockito-core:4.8.0' + testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' + testImplementation 'org.springframework.restdocs:spring-restdocs-asciidoctor' } tasks.named('test') { useJUnitPlatform() } + +// 변수 선언 +// snippetsDir 변수는 생성된 스니펫 파일을 저장할 디렉토리를 지정합니다. 스니펫 파일은 API 엔드포인트에서 생성된 예제 요청 및 응답 정보를 포함합니다. +ext { + snippetsDir = file('build/generated-snippets') +} + +// bootjar과 관련된 설정이며, 스니펫을 이용해 문서 작성 후, +// build - docs - asciidoc 하위에 생기는 html 파일을 static/docs로 복사해줍니다. +bootJar { + dependsOn asciidoctor + from("${asciidoctor.outputDir}/html5") + into 'static/docs' +} + +asciidoctor { + inputs.dir snippetsDir + dependsOn test +} + +configurations { + asciidoctorExtensions + compileOnly { + extendsFrom annotationProcessor + } +} + diff --git a/src/main/java/com/aiary/aiary/domain/diary/controller/DiaryController.java b/src/main/java/com/aiary/aiary/domain/diary/controller/DiaryController.java index 1935b71..c26cdbd 100644 --- a/src/main/java/com/aiary/aiary/domain/diary/controller/DiaryController.java +++ b/src/main/java/com/aiary/aiary/domain/diary/controller/DiaryController.java @@ -61,7 +61,7 @@ public ResponseEntity getMonthlyDiary(@AuthenticationPrincipal U @GetMapping("/search") public ResponseEntity searchDiariesByKeyWord(@AuthenticationPrincipal UserDetail user, @RequestParam(defaultValue = "0", required = false) int page, - @RequestParam(defaultValue = "10", required = false)int size, + @RequestParam(defaultValue = "40", required = false)int size, @RequestParam("diary_date") String diaryDate, @RequestParam String keyword){ PageRequest pageRequest = PageRequest.of(page, size); diff --git a/src/main/java/com/aiary/aiary/domain/diary/dto/mapper/DiaryMapper.java b/src/main/java/com/aiary/aiary/domain/diary/dto/mapper/DiaryMapper.java index ddb48b6..a399523 100644 --- a/src/main/java/com/aiary/aiary/domain/diary/dto/mapper/DiaryMapper.java +++ b/src/main/java/com/aiary/aiary/domain/diary/dto/mapper/DiaryMapper.java @@ -49,12 +49,12 @@ public MonthlyDiaryRes toMonthlyDiaryList(List monthlyDiaries){ } public SearchDiariesRes toDiarySlice(Slice diaries){ - List diaryInfos = diaries.stream() + List diaryRes = diaries.stream() .map(this::toEntity) .collect(Collectors.toList()); return SearchDiariesRes.builder() - .diaryInfos(diaryInfos) + .diaryRes(diaryRes) .curPageNumber(diaries.getNumber()) .hasNext(diaries.hasNext()) .hasPrevious(diaries.hasPrevious()) diff --git a/src/main/java/com/aiary/aiary/domain/diary/dto/request/DiaryCreateReq.java b/src/main/java/com/aiary/aiary/domain/diary/dto/request/DiaryCreateReq.java index 4707da6..5157b90 100644 --- a/src/main/java/com/aiary/aiary/domain/diary/dto/request/DiaryCreateReq.java +++ b/src/main/java/com/aiary/aiary/domain/diary/dto/request/DiaryCreateReq.java @@ -8,7 +8,9 @@ import java.time.LocalDate; @Getter +@Builder @NoArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) public class DiaryCreateReq { diff --git a/src/main/java/com/aiary/aiary/domain/diary/dto/response/SearchDiariesRes.java b/src/main/java/com/aiary/aiary/domain/diary/dto/response/SearchDiariesRes.java index 41c1f2d..3685c50 100644 --- a/src/main/java/com/aiary/aiary/domain/diary/dto/response/SearchDiariesRes.java +++ b/src/main/java/com/aiary/aiary/domain/diary/dto/response/SearchDiariesRes.java @@ -12,7 +12,7 @@ @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) public class SearchDiariesRes { - private List diaryInfos; + private List diaryRes; private int curPageNumber; private boolean hasNext; private boolean hasPrevious; diff --git a/src/main/java/com/aiary/aiary/domain/diary/service/DiaryService.java b/src/main/java/com/aiary/aiary/domain/diary/service/DiaryService.java index 80a3f1c..72c879c 100644 --- a/src/main/java/com/aiary/aiary/domain/diary/service/DiaryService.java +++ b/src/main/java/com/aiary/aiary/domain/diary/service/DiaryService.java @@ -10,7 +10,6 @@ import com.aiary.aiary.domain.user.entity.User; import com.aiary.aiary.domain.user.entity.UserDetail; import com.aiary.aiary.domain.user.exception.UnAuthorizedAccessException; -import com.aiary.aiary.domain.user.service.UserService; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.PageRequest; @@ -27,7 +26,6 @@ public class DiaryService { private static final String FIRST_DAY = "-01"; - private final UserService userService; private final DiaryRepository diaryRepository; private final DiaryMapper diaryMapper; @@ -37,9 +35,9 @@ public void createDiary(UserDetail userDetail, DiaryCreateReq diaryCreateReq, St } @Transactional - public void deleteDiary(UserDetail userDetail, Long diaryId){ + public void deleteDiary(UserDetail userDetail, Long diaryId){ Diary deleteDiary = findDiaryWithUser(diaryId); - User user = userService.findUserById(userDetail.getUserId()); + User user = userDetail.getUser(); if (!Objects.equals(user.getId(), deleteDiary.getUser().getId())) throw new UnAuthorizedAccessException(); diff --git a/src/test/java/com/aiary/aiary/domain/diary/controller/DiaryControllerTest.java b/src/test/java/com/aiary/aiary/domain/diary/controller/DiaryControllerTest.java new file mode 100644 index 0000000..9bf48c6 --- /dev/null +++ b/src/test/java/com/aiary/aiary/domain/diary/controller/DiaryControllerTest.java @@ -0,0 +1,154 @@ +package com.aiary.aiary.domain.diary.controller; + +import com.aiary.aiary.domain.diary.dto.request.DiaryCreateReq; +import com.aiary.aiary.domain.diary.dto.response.DiaryRes; +import com.aiary.aiary.domain.diary.dto.response.MonthlyDiaryRes; +import com.aiary.aiary.domain.diary.service.DiaryService; +import com.aiary.aiary.domain.user.entity.UserDetail; +import com.aiary.aiary.global.s3.service.S3UploadService; +import com.aiary.aiary.support.fixture.DiaryControllerFixture; +import com.aiary.aiary.support.utils.MockApiTest; +import com.aiary.aiary.support.utils.WithCustomMockUser; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.restdocs.RestDocumentationExtension; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.ResultActions; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static com.aiary.aiary.support.docs.ApiDocumentUtils.getDocumentRequest; +import static com.aiary.aiary.support.docs.ApiDocumentUtils.getDocumentResponse; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doNothing; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.multipart; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + + +@WebMvcTest(DiaryController.class) +@ExtendWith(RestDocumentationExtension.class) +@DisplayName("Diary 컨트롤러에 ") +class DiaryControllerTest extends MockApiTest { + + @MockBean + private DiaryService diaryService; + @MockBean + private S3UploadService s3UploadService; + + @Test + @WithCustomMockUser + @DisplayName("일기가 등록될 수 있다.") + void createDiary() throws Exception { + //given + MockMultipartFile file = new MockMultipartFile( + "file", // 파라미터 이름 + "file_name", // 파일 이름 + "image/jpeg",// 파일 타입 + "test file".getBytes(StandardCharsets.UTF_8) // 파일 내용 + ); + given(s3UploadService.saveFile(any(), any())).willReturn(String.valueOf(file)); + + DiaryCreateReq createReq = DiaryControllerFixture.CREATE_REQ; + String jsonByCreateReq = objectMapper.writeValueAsString(createReq); + MockMultipartFile request = new MockMultipartFile( + "createRequest", + "createRequest", + "application/json", + jsonByCreateReq.getBytes(StandardCharsets.UTF_8) + ); + String token = "accessToken"; + diaryService.createDiary(any(), any(), any()); + + //when + ResultActions perform = + mockMvc.perform( + multipart("/diaries") + .file(file) + .file(request) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + token) + ); + + // then + perform.andExpect(status().isOk()); + + // docs + perform.andDo(print()) + .andDo(document("register diary", + getDocumentRequest(), + getDocumentResponse())); + + } + + @Test + @DisplayName("사용자가 작성한 일기가 PK로 삭제될 수 있다.") + void deleteDiary() throws Exception { + //given + doNothing().when(diaryService).deleteDiary(any(UserDetail.class), any(Long.class)); + + //when + ResultActions perform = + mockMvc.perform( + delete("/diaries/{diaryId}", 1L)); + //then + perform.andExpect(status().isOk()); + + //docs + perform.andDo(print()) + .andDo(document("delete diary", + getDocumentRequest(), + getDocumentResponse())); + } + + @Test + @DisplayName("사용자가 작성한 일기를 달별로 조회할 수 있다.") + void getMonthlyDiary() throws Exception { + // given + String diaryDate = "2023-09"; + List expectedDiaries = DiaryControllerFixture.MONTHLY_DIARIES(); + MonthlyDiaryRes monthlyDiaryRes = MonthlyDiaryRes.builder() + .monthlyDiaryRes(expectedDiaries) + .build(); + given(diaryService.findMonthlyDiaryByDate(any(UserDetail.class), eq(diaryDate))).willReturn(monthlyDiaryRes); + + // when + ResultActions perform = mockMvc.perform( + get("/diaries") + .param("diary_date", diaryDate) + .contentType(MediaType.APPLICATION_JSON)); + + // then + perform.andExpect(status().isOk()) + .andDo(print()) + .andDo(document("get monthlyDiaries", + getDocumentRequest(), + getDocumentResponse(), + responseFields( + fieldWithPath("code").description("상태 코드"), + fieldWithPath("message").description("응답 메시지"), + fieldWithPath("data").description("응답 데이터").type(JsonFieldType.OBJECT).optional(), + fieldWithPath("data.monthly_diary_info").description("월별 일기 정보").type(JsonFieldType.ARRAY).optional(), + fieldWithPath("data.monthly_diary_info[].diary_id").type(JsonFieldType.NUMBER).description("일기 ID").optional(), + fieldWithPath("data.monthly_diary_info[].title").type(JsonFieldType.STRING).description("일기 제목").optional(), + fieldWithPath("data.monthly_diary_info[].weather").type(JsonFieldType.STRING).description("일기 날씨").optional(), + fieldWithPath("data.monthly_diary_info[].emoji").type(JsonFieldType.STRING).description("이모지").optional(), + fieldWithPath("data.monthly_diary_info[].contents").type(JsonFieldType.STRING).description("일기 내용").optional(), + fieldWithPath("data.monthly_diary_info[].diary_date").type(JsonFieldType.STRING).description("일기 날짜").optional(), + fieldWithPath("data.monthly_diary_info[].drawing_url").type(JsonFieldType.STRING).description("일기 그림 URL").optional()) + )); + } + +} \ No newline at end of file diff --git a/src/test/java/com/aiary/aiary/domain/diary/service/DiaryServiceTest.java b/src/test/java/com/aiary/aiary/domain/diary/service/DiaryServiceTest.java new file mode 100644 index 0000000..936ea7f --- /dev/null +++ b/src/test/java/com/aiary/aiary/domain/diary/service/DiaryServiceTest.java @@ -0,0 +1,120 @@ +package com.aiary.aiary.domain.diary.service; + +import com.aiary.aiary.domain.diary.dto.request.DiaryCreateReq; +import com.aiary.aiary.domain.diary.dto.response.DiaryRes; +import com.aiary.aiary.domain.diary.dto.response.MonthlyDiaryRes; +import com.aiary.aiary.domain.diary.dto.response.SearchDiariesRes; +import com.aiary.aiary.domain.diary.entity.Diary; +import com.aiary.aiary.domain.diary.exception.DiaryNotFoundException; +import com.aiary.aiary.domain.diary.repository.DiaryRepository; +import com.aiary.aiary.domain.user.entity.UserDetail; +import com.aiary.aiary.domain.user.exception.UnAuthorizedAccessException; +import com.aiary.aiary.domain.user.repository.UserRepository; +import com.aiary.aiary.support.database.DatabaseTest; +import com.aiary.aiary.support.fixture.DiaryServiceFixture; +import com.aiary.aiary.support.fixture.UserFixture; +import org.junit.jupiter.api.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; + + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.assertj.core.api.Assertions.*; +@DatabaseTest +@DisplayName("Diary 서비스에 ") +class DiaryServiceTest { + + @Autowired + private DiaryService diaryService; + @Autowired + private DiaryRepository diaryRepository; + @Autowired + private UserRepository userRepository; + + @Test + @DisplayName("일기가 생성할 수 있다.") + void createDiary(){ + //given + userRepository.save(UserFixture.DIARY_CREATE_USER); + UserDetail userDetail = UserFixture.DIARY_CREATE_USERDETAIL; + DiaryCreateReq diaryCreateReq = DiaryServiceFixture.DIARY_CREATE_REQ; + String diaryUrl = "url"; + Diary expect = diaryRepository.save(DiaryServiceFixture.CREATE_DIARY); + + //when + diaryService.createDiary(userDetail, diaryCreateReq, diaryUrl); + Diary actual = diaryService.findDiaryWithUser(expect.getId()); + + //then + assertAll( + () -> assertThat(actual.getContents()).isEqualTo(expect.getContents()), + () -> assertThat(actual.getContents()).isEqualTo(expect.getContents()), + () -> assertThat(actual.getDiaryDate()).isEqualTo(expect.getDiaryDate()), + () -> assertThat(actual.getEmoji()).isEqualTo(expect.getEmoji()), + () -> assertThat(actual.getDrawingUrl()).isEqualTo(expect.getDrawingUrl()), + () -> assertThat(actual.getWeather()).isEqualTo(expect.getWeather()) + ); + } + + @Test + @DisplayName("사용자가 작성한 일기가 PK로 삭제가 될 수 있다.") + void deleteDiary(){ + //given + userRepository.save(UserFixture.DIARY_DELETE_USER); + userRepository.save(UserFixture.DIARY_DELETE_UNAUTHOR_USER); + Diary diary = diaryRepository.save(DiaryServiceFixture.DELETE_DIARY); + Diary unAuthorDiary = diaryRepository.save(DiaryServiceFixture.DELETE_UNAUTHOR_DIARY); + + //when + diaryService.deleteDiary(UserFixture.DIAEY_DELETE_USERDETAIL, diary.getId()); + + //then + assertAll( + () -> assertThat(diary.isDeleted()).isTrue(), + () -> assertThatThrownBy(() -> diaryService.findDiaryWithUser(diary.getId())) + .isInstanceOf(DiaryNotFoundException.class), + () -> assertThatThrownBy(() -> diaryService.deleteDiary(UserFixture.DIAEY_DELETE_UNAUTHOR_USERDETAIL, unAuthorDiary.getId())) + .isInstanceOf(UnAuthorizedAccessException.class) + .hasMessageContaining("권한이 없는 사용자") + ); + } + + @Test + @DisplayName("일기들을 달 별로 조회할 수 있다.") + void findMonthlyDiaryByDate(){ + //given + userRepository.save(UserFixture.DIARY_FIND_MONTH_USER); + diaryRepository.saveAll(DiaryServiceFixture.INSERT_FIND_DIARIES()); + + //when + MonthlyDiaryRes monthlyDiaryRes = diaryService.findMonthlyDiaryByDate(UserFixture.DIARY_FIND_MONTH_USERDETAIL, "2023-09"); + List diaryRes = monthlyDiaryRes.getMonthlyDiaryRes(); + + //then + assertThat(diaryRes.size()).isEqualTo(2); + assertThat(diaryRes.get(0).getTitle()).isEqualTo("일기 제목2"); + assertThat(diaryRes.get(1).getTitle()).isEqualTo("일기 제목1"); + } + + @Test + @DisplayName("키워드로 제목,내용을 포함한 일기들을 검색할 수 있다.") + void searchDiariesByDate(){ + //given + userRepository.save(UserFixture.DIARY_SEARCH_USER); + diaryRepository.saveAll(DiaryServiceFixture.INSERT_SEARCH_DIARIES()); + PageRequest pageRequest = PageRequest.of(0, 40); + + //when + SearchDiariesRes searchDiariesRes = diaryService.searchDiariesByKeyword(UserFixture.DIARY_SEARCH_USERDETAIL + , pageRequest, "2023-09", "내용"); + List diaryRes = searchDiariesRes.getDiaryRes(); + + //then + assertThat(diaryRes.size()).isEqualTo(2); + assertThat(diaryRes.get(0).getContents()).isEqualTo("일기 내용2"); + assertThat(diaryRes.get(1).getContents()).isEqualTo("일기 내용1"); + } + +} diff --git a/src/test/java/com/aiary/aiary/support/config/TestProfile.java b/src/test/java/com/aiary/aiary/support/config/TestProfile.java new file mode 100644 index 0000000..1258ef5 --- /dev/null +++ b/src/test/java/com/aiary/aiary/support/config/TestProfile.java @@ -0,0 +1,5 @@ +package com.aiary.aiary.support.config; + +public interface TestProfile { + String TEST = "test"; +} \ No newline at end of file diff --git a/src/test/java/com/aiary/aiary/support/database/DatabaseTest.java b/src/test/java/com/aiary/aiary/support/database/DatabaseTest.java new file mode 100644 index 0000000..5bffc08 --- /dev/null +++ b/src/test/java/com/aiary/aiary/support/database/DatabaseTest.java @@ -0,0 +1,18 @@ +package com.aiary.aiary.support.database; + +import com.aiary.aiary.support.config.TestProfile; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import javax.transaction.Transactional; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Transactional +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) +@ActiveProfiles(TestProfile.TEST) +public @interface DatabaseTest {} diff --git a/src/test/java/com/aiary/aiary/support/docs/ApiDocumentUtils.java b/src/test/java/com/aiary/aiary/support/docs/ApiDocumentUtils.java new file mode 100644 index 0000000..39ce8e5 --- /dev/null +++ b/src/test/java/com/aiary/aiary/support/docs/ApiDocumentUtils.java @@ -0,0 +1,23 @@ +package com.aiary.aiary.support.docs; + +import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor; +import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor; + +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +public interface ApiDocumentUtils { + + // OperationRequestPreprocessor 는 API 문서화를 위해 요청에 대한 사전처리를 수행 + // prettyPrint() 메서드는 요청/응답을 보기 좋게 만듬. + // 문서상 uri 를 기본값인 http://localhost:8080 에서 https://docs.api.com 으로 변경하기 위해 사용합니다. + static OperationRequestPreprocessor getDocumentRequest() { + return preprocessRequest(modifyUris() + .scheme("https") + .host("docs.api.com") + .removePort(), prettyPrint()); + } + + // OperationResponsePreprocessor 는 API 문서화를 위해 응답에 대한 사전처리를 수행 + static OperationResponsePreprocessor getDocumentResponse() { + return preprocessResponse(prettyPrint()); + } +} diff --git a/src/test/java/com/aiary/aiary/support/docs/RestDocsConfiguration.java b/src/test/java/com/aiary/aiary/support/docs/RestDocsConfiguration.java new file mode 100644 index 0000000..d2c930f --- /dev/null +++ b/src/test/java/com/aiary/aiary/support/docs/RestDocsConfiguration.java @@ -0,0 +1,21 @@ +package com.aiary.aiary.support.docs; + +import org.springframework.boot.test.autoconfigure.restdocs.RestDocsMockMvcConfigurationCustomizer; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; + +import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; + +@TestConfiguration +public class RestDocsConfiguration { + + // Spring Rest Docs + MockMvc 함께 사용을 위한 빈 설정 + @Bean + public RestDocsMockMvcConfigurationCustomizer restDocsMockMvcConfigurationCustomizer() { + return configurer -> + configurer + .operationPreprocessors() + .withRequestDefaults(prettyPrint()) + .withResponseDefaults(prettyPrint()); + } +} \ No newline at end of file diff --git a/src/test/java/com/aiary/aiary/support/fixture/DiaryControllerFixture.java b/src/test/java/com/aiary/aiary/support/fixture/DiaryControllerFixture.java new file mode 100644 index 0000000..bdf0f3c --- /dev/null +++ b/src/test/java/com/aiary/aiary/support/fixture/DiaryControllerFixture.java @@ -0,0 +1,35 @@ +package com.aiary.aiary.support.fixture; + +import com.aiary.aiary.domain.diary.dto.request.DiaryCreateReq; +import com.aiary.aiary.domain.diary.dto.response.DiaryRes; +import com.aiary.aiary.domain.diary.entity.Weather; + +import java.time.LocalDate; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class DiaryControllerFixture { + + public static final DiaryCreateReq CREATE_REQ = DiaryCreateReq.builder() + .title("일기 제목") + .contents("일기 내용") + .diaryDate(LocalDate.of(2023, 9, 24)) + .weather("SUNNY") + .emoji("\uD83D\uDCAA") + .build(); + + public static List MONTHLY_DIARIES() { + return IntStream.range(0, 5) + .mapToObj(i -> DiaryRes.builder() + .diaryId((long) i) + .title("일기 제목" + i) + .weather(Weather.SUNNY) + .emoji("\uD83D\uDCAA") + .contents("일기 내용" + i) + .drawingUrl("url" + i) + .diaryDate("2023-09-0" + (i + 1)) + .build()) + .collect(Collectors.toList()); + } +} diff --git a/src/test/java/com/aiary/aiary/support/fixture/DiaryServiceFixture.java b/src/test/java/com/aiary/aiary/support/fixture/DiaryServiceFixture.java new file mode 100644 index 0000000..1e874d3 --- /dev/null +++ b/src/test/java/com/aiary/aiary/support/fixture/DiaryServiceFixture.java @@ -0,0 +1,106 @@ +package com.aiary.aiary.support.fixture; + +import com.aiary.aiary.domain.diary.dto.request.DiaryCreateReq; +import com.aiary.aiary.domain.diary.entity.Diary; +import com.aiary.aiary.domain.diary.entity.Weather; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +public class DiaryServiceFixture { + + public static final Diary CREATE_DIARY = Diary.builder() + .title("일기 제목") + .weather(Weather.SUNNY) + .emoji("\uD83D\uDCAA") + .contents("일기 내용") + .drawingUrl("url") + .diaryDate(LocalDate.of(2023,9,19)) + .user(UserFixture.DIARY_CREATE_USER) + .build(); + + public static final Diary DELETE_DIARY = Diary.builder() + .title("일기 제목") + .weather(Weather.SUNNY) + .emoji("\uD83D\uDCAA") + .contents("일기 내용") + .drawingUrl("url") + .diaryDate(LocalDate.of(2023,9,20)) + .user(UserFixture.DIARY_DELETE_USER) + .build(); + + public static final Diary DELETE_UNAUTHOR_DIARY = Diary.builder() + .title("일기 제목") + .weather(Weather.SUNNY) + .emoji("\uD83D\uDCAA") + .contents("일기 내용") + .drawingUrl("url") + .diaryDate(LocalDate.of(2023,9,20)) + .user(UserFixture.DIARY_DELETE_USER) + .build(); + + public static final DiaryCreateReq DIARY_CREATE_REQ = DiaryCreateReq.builder() + .title("일기 제목") + .weather("SUNNY") + .emoji("\uD83D\uDCAA") + .contents("일기 내용") + .diaryDate(LocalDate.of(2023,9,19)) + .build(); + + public static List INSERT_FIND_DIARIES() { + List diaries = new ArrayList<>(); + Diary diary1 = Diary.builder() + .title("일기 제목1") + .weather(Weather.SUNNY) + .emoji("\uD83D\uDCAA") + .contents("일기 내용1") + .drawingUrl("url1") + .diaryDate(LocalDate.of(2023,9,21)) + .user(UserFixture.DIARY_FIND_MONTH_USER) + .build(); + + Diary diary2 = Diary.builder() + .title("일기 제목2") + .weather(Weather.SUNNY) + .emoji("\uD83D\uDCAA") + .contents("일기 내용2") + .drawingUrl("url1") + .diaryDate(LocalDate.of(2023,9,22)) + .user(UserFixture.DIARY_FIND_MONTH_USER) + .build(); + + diaries.add(diary1); + diaries.add(diary2); + + return diaries; + } + + public static List INSERT_SEARCH_DIARIES() { + List diaries = new ArrayList<>(); + Diary diary1 = Diary.builder() + .title("일기 제목1") + .weather(Weather.SUNNY) + .emoji("\uD83D\uDCAA") + .contents("일기 내용1") + .drawingUrl("url1") + .diaryDate(LocalDate.of(2023,9,21)) + .user(UserFixture.DIARY_SEARCH_USER) + .build(); + + Diary diary2 = Diary.builder() + .title("일기 제목2") + .weather(Weather.SUNNY) + .emoji("\uD83D\uDCAA") + .contents("일기 내용2") + .drawingUrl("url1") + .diaryDate(LocalDate.of(2023,9,22)) + .user(UserFixture.DIARY_SEARCH_USER) + .build(); + + diaries.add(diary1); + diaries.add(diary2); + + return diaries; + } +} diff --git a/src/test/java/com/aiary/aiary/support/fixture/UserFixture.java b/src/test/java/com/aiary/aiary/support/fixture/UserFixture.java new file mode 100644 index 0000000..16102d4 --- /dev/null +++ b/src/test/java/com/aiary/aiary/support/fixture/UserFixture.java @@ -0,0 +1,63 @@ +package com.aiary.aiary.support.fixture; + +import com.aiary.aiary.domain.user.entity.Role; +import com.aiary.aiary.domain.user.entity.User; +import com.aiary.aiary.domain.user.entity.UserDetail; + +public class UserFixture { + + public static final User DIARY_CREATE_USER = User.builder() + .email("Create@gmail.com") + .password("Test012@") + .nickname("테스트유저") + .role(Role.USER) + .build(); + + public static final UserDetail DIARY_CREATE_USERDETAIL = UserDetail.builder() + .user(DIARY_CREATE_USER) + .build(); + + public static final User DIARY_DELETE_USER = User.builder() + .email("Delete@gmail.com") + .password("Test012@") + .nickname("테스트유저") + .role(Role.USER) + .build(); + + public static final UserDetail DIAEY_DELETE_USERDETAIL = UserDetail.builder() + .user(DIARY_DELETE_USER) + .build(); + + public static final User DIARY_DELETE_UNAUTHOR_USER = User.builder() + .email("Unauthor@gmail.com") + .password("Test012@") + .nickname("테스트유저") + .role(Role.USER) + .build(); + + public static final UserDetail DIAEY_DELETE_UNAUTHOR_USERDETAIL = UserDetail.builder() + .user(DIARY_DELETE_UNAUTHOR_USER) + .build(); + + public static final User DIARY_FIND_MONTH_USER = User.builder() + .email("Find@gmail.com") + .password("Test012@") + .nickname("테스트유저") + .role(Role.USER) + .build(); + + public static final UserDetail DIARY_FIND_MONTH_USERDETAIL = UserDetail.builder() + .user(DIARY_FIND_MONTH_USER) + .build(); + + public static final User DIARY_SEARCH_USER = User.builder() + .email("Search@gmail.com") + .password("Test012@") + .nickname("테스트유저") + .role(Role.USER) + .build(); + + public static final UserDetail DIARY_SEARCH_USERDETAIL = UserDetail.builder() + .user(DIARY_SEARCH_USER) + .build(); +} diff --git a/src/test/java/com/aiary/aiary/support/utils/MockApiTest.java b/src/test/java/com/aiary/aiary/support/utils/MockApiTest.java new file mode 100644 index 0000000..720e427 --- /dev/null +++ b/src/test/java/com/aiary/aiary/support/utils/MockApiTest.java @@ -0,0 +1,48 @@ +package com.aiary.aiary.support.utils; + +import com.aiary.aiary.AiaryApplication; +import com.aiary.aiary.support.config.TestProfile; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +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; + +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = AiaryApplication.class) +@ActiveProfiles(TestProfile.TEST) +@Disabled +public abstract class MockApiTest { + + protected MockMvc mockMvc; + protected ObjectMapper objectMapper = buildObjectMapper(); + + private ObjectMapper buildObjectMapper() { + final ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerModule(new JavaTimeModule()); + return objectMapper; + } + + @BeforeEach + public void setupMockMvc( + WebApplicationContext ctx, + RestDocumentationContextProvider restDocumentationContextProvider) { + mockMvc = + MockMvcBuilders.webAppContextSetup(ctx) + .apply(documentationConfiguration(restDocumentationContextProvider)) + .addFilter(new CharacterEncodingFilter("UTF-8", true)) + .alwaysDo(print()) + .build(); + } +} diff --git a/src/test/java/com/aiary/aiary/support/utils/WithCustomMockUser.java b/src/test/java/com/aiary/aiary/support/utils/WithCustomMockUser.java new file mode 100644 index 0000000..a66d11f --- /dev/null +++ b/src/test/java/com/aiary/aiary/support/utils/WithCustomMockUser.java @@ -0,0 +1,13 @@ +package com.aiary.aiary.support.utils; + +import org.springframework.security.test.context.support.WithSecurityContext; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@WithSecurityContext(factory = WithCustomMockUserSecurityContextFactory.class) +public @interface WithCustomMockUser { + String userUuid() default "test@email"; + String role() default "USER"; +} diff --git a/src/test/java/com/aiary/aiary/support/utils/WithCustomMockUserSecurityContextFactory.java b/src/test/java/com/aiary/aiary/support/utils/WithCustomMockUserSecurityContextFactory.java new file mode 100644 index 0000000..2c6884e --- /dev/null +++ b/src/test/java/com/aiary/aiary/support/utils/WithCustomMockUserSecurityContextFactory.java @@ -0,0 +1,35 @@ +package com.aiary.aiary.support.utils; + +import com.aiary.aiary.domain.user.entity.Role; +import com.aiary.aiary.domain.user.entity.User; +import com.aiary.aiary.domain.user.entity.UserDetail; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.test.context.support.WithSecurityContextFactory; + +import java.util.List; + +public class WithCustomMockUserSecurityContextFactory implements WithSecurityContextFactory { + + @Override + public SecurityContext createSecurityContext(WithCustomMockUser annotation) { + String userEmail = annotation.userUuid(); + String role = annotation.role(); + + User user = User.builder() + .email(userEmail) + .nickname("테스트 유저") + .password("Test012@") + .role(Role.valueOf(role)) + .build(); + UserDetail userDetail = UserDetail.builder().user(user).build(); + + UsernamePasswordAuthenticationToken token = + new UsernamePasswordAuthenticationToken(userDetail, "password", List.of(new SimpleGrantedAuthority(role))); + SecurityContext context = SecurityContextHolder.getContext(); + context.setAuthentication(token); + return context; + } +}