diff --git a/api-module/src/main/java/hongik/triple/apimodule/application/member/MemberService.java b/api-module/src/main/java/hongik/triple/apimodule/application/member/MemberService.java index 03dd106..f1bc234 100644 --- a/api-module/src/main/java/hongik/triple/apimodule/application/member/MemberService.java +++ b/api-module/src/main/java/hongik/triple/apimodule/application/member/MemberService.java @@ -83,12 +83,14 @@ public MemberRes getProfile(Member member) { @Transactional public MemberRes updateProfile(Member member, MemberReq memberReq) { member.updateSkinType(memberReq.skin_type()); + member.updateName(memberReq.name()); Member updateMember = memberRepository.save(member); return MemberRes.builder() .id(updateMember.getMemberId()) .email(updateMember.getEmail()) .name(updateMember.getName()) + .skinType(updateMember.getSkinType()) .build(); } diff --git a/api-module/src/test/java/hongik/triple/apimodule/analysis/AnalysisServiceTest.java b/api-module/src/test/java/hongik/triple/apimodule/analysis/AnalysisServiceTest.java new file mode 100644 index 0000000..6ef5bc6 --- /dev/null +++ b/api-module/src/test/java/hongik/triple/apimodule/analysis/AnalysisServiceTest.java @@ -0,0 +1,514 @@ +package hongik.triple.apimodule.analysis; + +import hongik.triple.apimodule.application.analysis.AnalysisService; +import hongik.triple.commonmodule.dto.analysis.*; +import hongik.triple.commonmodule.enumerate.AcneType; +import hongik.triple.commonmodule.enumerate.MemberType; +import hongik.triple.domainmodule.domain.analysis.Analysis; +import hongik.triple.domainmodule.domain.analysis.repository.AnalysisRepository; +import hongik.triple.domainmodule.domain.member.Member; +import hongik.triple.inframodule.ai.AIClient; +import hongik.triple.inframodule.naver.NaverClient; +import hongik.triple.inframodule.s3.S3Client; +import hongik.triple.inframodule.youtube.YoutubeClient; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.multipart.MultipartFile; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +@DisplayName("AnalysisService 테스트") +class AnalysisServiceTest { + + @Mock + private AIClient aiClient; + + @Mock + private YoutubeClient youtubeClient; + + @Mock + private NaverClient naverClient; + + @Mock + private AnalysisRepository analysisRepository; + + @Mock + private S3Client s3Client; + + @InjectMocks + private AnalysisService analysisService; + + private Member member; + + @BeforeEach + void setup() { + member = new Member("user", "email@test.com", MemberType.KAKAO); + ReflectionTestUtils.setField(member, "memberId", 1L); + } + + private MultipartFile mockFile() { + return new MockMultipartFile("file", "test.jpg", "image/jpeg", "dummy".getBytes()); + } + + @Nested + @DisplayName("performAnalysis()는") + class PerformAnalysisTest { + + @Test + @DisplayName("유효한 파일을 분석하고 결과를 반환한다.") + void success() { + MultipartFile file = mockFile(); + + AnalysisData mockData = mock(AnalysisData.class); + YoutubeVideoDto videoDto = new YoutubeVideoDto( + "id1", + "title1", + "url1", + "channel1", + "thumb1" + ); + + NaverProductDto productDto = new NaverProductDto( + "p1", + "상품1", + "url", + 1000, + "img", + "category", + "mall", + "brand" + ); + + given(s3Client.uploadImage(file, "skin")).willReturn("s3/image.png"); + given(aiClient.sendPredictRequest(file)).willReturn(mockData); + given(mockData.labelToSkinType()).willReturn(AcneType.PAPULES); + + given(youtubeClient.searchVideos(anyString(), eq(3))) + .willReturn(List.of(videoDto)); + given(naverClient.searchProducts(anyString(), eq(3))) + .willReturn(List.of(productDto)); + + Analysis saved = Analysis.builder() + .member(member) + .acneType(AcneType.PAPULES) + .imageUrl("s3/image.png") + .isPublic(true) + .videoData(List.of(videoDto)) + .productData(List.of(productDto)) + .build(); + ReflectionTestUtils.setField(saved, "analysisId", 10L); + ReflectionTestUtils.setField(saved, "createdAt", LocalDateTime.now()); + + given(analysisRepository.save(any())).willReturn(saved); + given(s3Client.getImage("s3/image.png")).willReturn("https://cdn/image.png"); + + AnalysisRes res = analysisService.performAnalysis(member, file); + + assertThat(res.analysisId()).isEqualTo(10L); + assertThat(res.acneType()).isEqualTo("PAPULES"); + assertThat(res.imageUrl()).isEqualTo("https://cdn/image.png"); + + verify(analysisRepository, times(1)).save(any()); + } + + @Test + @DisplayName("파일이 비어 있으면 예외를 던진다.") + void emptyFile() { + MultipartFile empty = new MockMultipartFile("file", new byte[]{}); + + assertThatThrownBy(() -> analysisService.performAnalysis(member, empty)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("File is empty"); + + verify(analysisRepository, never()).save(any()); + } + } + + @Nested + @DisplayName("getAnalysisDetail()은") + class GetAnalysisDetailTest { + + @Test + @DisplayName("자신의 분석 결과를 조회한다.") + void success() { + Analysis analysis = Analysis.builder() + .member(member) + .acneType(AcneType.COMEDONES) + .imageUrl("img.jpg") + .isPublic(true) + .videoData(List.of()) + .productData(List.of()) + .build(); + ReflectionTestUtils.setField(analysis, "analysisId", 20L); + ReflectionTestUtils.setField(analysis, "createdAt", LocalDateTime.now()); + + given(analysisRepository.findById(20L)).willReturn(Optional.of(analysis)); + given(s3Client.getImage("img.jpg")).willReturn("cdn/img.jpg"); + + AnalysisRes res = analysisService.getAnalysisDetail(member, 20L); + + assertThat(res.analysisId()).isEqualTo(20L); + assertThat(res.acneType()).isEqualTo("COMEDONES"); + } + + @Test + @DisplayName("다른 사용자의 결과를 조회 시 예외 발생") + void unauthorized() { + Member another = new Member("other", "other@test.com", MemberType.GOOGLE); + ReflectionTestUtils.setField(another, "memberId", 99L); + + Analysis analysis = Analysis.builder() + .member(another) + .acneType(AcneType.PAPULES) + .imageUrl("img") + .isPublic(true) + .videoData(List.of()) + .productData(List.of()) + .build(); + ReflectionTestUtils.setField(analysis, "analysisId", 20L); + + given(analysisRepository.findById(20L)).willReturn(Optional.of(analysis)); + + assertThatThrownBy(() -> analysisService.getAnalysisDetail(member, 20L)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Unauthorized"); + } + } + + @Nested + @DisplayName("getAnalysisListForMainPage()는") + class GetAnalysisListForMainPageTest { + + @Test + @DisplayName("최신 공개된 분석 3개와 유형별 개수를 반환한다.") + void success() { + Analysis a1 = Analysis.builder() + .member(member) + .acneType(AcneType.COMEDONES) + .imageUrl("img1") + .isPublic(true) + .videoData(List.of()) + .productData(List.of()) + .build(); + + ReflectionTestUtils.setField(a1, "analysisId", 1L); + ReflectionTestUtils.setField(a1, "createdAt", LocalDateTime.now()); + + given(analysisRepository.findTop3ByIsPublicTrueOrderByCreatedAtDesc()) + .willReturn(List.of(a1)); + + given(analysisRepository.countByAcneTypeAndIsPublicTrue("COMEDONES")).willReturn(3); + given(analysisRepository.countByAcneTypeAndIsPublicTrue("PUSTULES")).willReturn(1); + given(analysisRepository.countByAcneTypeAndIsPublicTrue("PAPULES")).willReturn(2); + given(analysisRepository.countByAcneTypeAndIsPublicTrue("FOLLICULITIS")).willReturn(1); + + given(s3Client.getImage("img1")).willReturn("cdn/img1"); + + MainLogRes res = analysisService.getAnalysisListForMainPage(); + + assertThat(res.comedones()).isEqualTo(3); + assertThat(res.analysisRes()).hasSize(1); + } + } + + @Nested + @DisplayName("getAnalysisPaginationForLogPage()는") + class GetPaginationForLogPageTest { + + @Test + @DisplayName("ALL이면 전체 공개 분석을 조회한다.") + void successAll() { + Pageable pageable = PageRequest.of(0, 10); + + Analysis a = Analysis.builder() + .member(member) + .acneType(AcneType.PAPULES) + .imageUrl("img") + .isPublic(true) + .videoData(List.of()) + .productData(List.of()) + .build(); + + ReflectionTestUtils.setField(a, "analysisId", 1L); + ReflectionTestUtils.setField(a, "createdAt", LocalDateTime.now()); + + Page page = new PageImpl<>(List.of(a)); + + given(analysisRepository.findByIsPublicTrueOrderByCreatedAtDesc(pageable)) + .willReturn(page); + + Page res = analysisService.getAnalysisPaginationForLogPage("ALL", pageable); + + assertThat(res.getContent()).hasSize(1); + } + + @Test + @DisplayName("특정 타입이면 타입별 공개 분석을 조회한다.") + void successType() { + Pageable pageable = PageRequest.of(0, 10); + + Analysis a = Analysis.builder() + .member(member) + .acneType(AcneType.PUSTULES) + .imageUrl("img") + .isPublic(true) + .videoData(List.of()) + .productData(List.of()) + .build(); + + ReflectionTestUtils.setField(a, "analysisId", 1L); + ReflectionTestUtils.setField(a, "createdAt", LocalDateTime.now()); + + Page page = new PageImpl<>(List.of(a)); + + given(analysisRepository.findByIsPublicTrueAndAcneTypeOrderByCreatedAtDesc("PUSTULES", pageable)) + .willReturn(page); + + Page res = analysisService.getAnalysisPaginationForLogPage("PUSTULES", pageable); + + assertThat(res.getContent()).hasSize(1); + } + + @Test + @DisplayName("유효하지 않은 타입이면 예외 발생") + void invalidType() { + Pageable pageable = PageRequest.of(0, 10); + + assertThatThrownBy(() -> analysisService.getAnalysisPaginationForLogPage("INVALID", pageable)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Invalid acne type"); + } + } + + @Nested + @DisplayName("getAnalysisListForMyPage()는") + class GetListForMyPageTest { + + @Test + @DisplayName("member가 null이면 예외 발생") + void nullMember() { + Pageable pageable = PageRequest.of(0, 10); + assertThatThrownBy(() -> analysisService.getAnalysisListForMyPage(null, "ALL", pageable)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("ALL이면 내 전체 분석을 조회한다.") + void successAll() { + Pageable pageable = PageRequest.of(0, 10); + + Analysis a = Analysis.builder() + .member(member) + .acneType(AcneType.FOLLICULITIS) + .imageUrl("img") + .isPublic(true) + .videoData(List.of()) + .productData(List.of()) + .build(); + + ReflectionTestUtils.setField(a, "analysisId", 1L); + ReflectionTestUtils.setField(a, "createdAt", LocalDateTime.now()); + + Page page = new PageImpl<>(List.of(a)); + + given(analysisRepository.findByMemberOrderByCreatedAtDesc(member, pageable)) + .willReturn(page); + + Page res = analysisService.getAnalysisListForMyPage(member, "ALL", pageable); + + assertThat(res.getContent()).hasSize(1); + } + + @Test + @DisplayName("타입별 조회 성공") + void successType() { + Pageable pageable = PageRequest.of(0, 10); + + Analysis a = Analysis.builder() + .member(member) + .acneType(AcneType.PUSTULES) + .imageUrl("img") + .videoData(List.of()) + .productData(List.of()) + .isPublic(true) + .build(); + + ReflectionTestUtils.setField(a, "analysisId", 1L); + ReflectionTestUtils.setField(a, "createdAt", LocalDateTime.now()); + + Page page = new PageImpl<>(List.of(a)); + + given(analysisRepository.findByMemberAndAcneTypeOrderByCreatedAtDesc(member, "PUSTULES", pageable)) + .willReturn(page); + + Page res = analysisService.getAnalysisListForMyPage(member, "PUSTULES", pageable); + + assertThat(res.getContent()).hasSize(1); + } + } + + @Nested + @DisplayName("getLogDetail()은") + class GetLogDetailTest { + + @Test + @DisplayName("분석 로그 상세를 반환한다.") + void success() { + Analysis a = Analysis.builder() + .member(member) + .acneType(AcneType.COMEDONES) + .imageUrl("img") + .isPublic(true) + .videoData(List.of()) + .productData(List.of()) + .build(); + ReflectionTestUtils.setField(a, "analysisId", 40L); + ReflectionTestUtils.setField(a, "createdAt", LocalDateTime.now()); + + given(analysisRepository.findById(40L)).willReturn(Optional.of(a)); + given(s3Client.getImage("img")).willReturn("cdn/img"); + + AnalysisLogRes res = analysisService.getLogDetail(40L); + + assertThat(res.analysisId()).isEqualTo(40L); + assertThat(res.userName()).isEqualTo(member.getName()); + } + + @Test + @DisplayName("존재하지 않는 로그 조회 시 예외 발생") + void notFound() { + given(analysisRepository.findById(123L)).willReturn(Optional.empty()); + + assertThatThrownBy(() -> analysisService.getLogDetail(123L)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Analysis not found"); + } + } + + @Nested + @DisplayName("updateIsPublic()은") + class UpdateIsPublicTest { + + @Test + @DisplayName("공개 여부를 수정하고 반환한다.") + void success() { + AnalysisReq req = new AnalysisReq(10L, false); + + Analysis a = Analysis.builder() + .member(member) + .acneType(AcneType.PAPULES) + .imageUrl("img") + .isPublic(true) + .videoData(List.of()) + .productData(List.of()) + .build(); + ReflectionTestUtils.setField(a, "analysisId", 10L); + ReflectionTestUtils.setField(a, "createdAt", LocalDateTime.now()); + + given(analysisRepository.findById(10L)).willReturn(Optional.of(a)); + given(s3Client.getImage("img")).willReturn("cdn/img"); + + AnalysisRes res = analysisService.updateIsPublic(member, req); + + assertThat(res.isPublic()).isFalse(); + } + + @Test + @DisplayName("다른 사용자가 수정하면 예외 발생") + void unauthorized() { + Member attacker = new Member("attacker", "a@test.com", MemberType.GOOGLE); + ReflectionTestUtils.setField(attacker, "memberId", 99L); + + AnalysisReq req = new AnalysisReq(10L, true); + + Analysis target = Analysis.builder() + .member(attacker) + .acneType(AcneType.PAPULES) + .imageUrl("img") + .isPublic(false) + .videoData(List.of()) + .productData(List.of()) + .build(); + ReflectionTestUtils.setField(target, "analysisId", 10L); + + given(analysisRepository.findById(10L)).willReturn(Optional.of(target)); + + assertThatThrownBy(() -> analysisService.updateIsPublic(member, req)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Unauthorized"); + } + } + + @Nested + @DisplayName("getYoutubeVideos()는") + class YoutubeVideosTest { + + @Test + @DisplayName("피부관리 키워드로 영상 3개를 검색한다.") + void success() { + YoutubeVideoDto video = new YoutubeVideoDto( + "1", + "title", + "url", + "channel", + "thumb" + ); + + given(youtubeClient.searchVideos("피부관리", 3)) + .willReturn(List.of(video)); + + List res = analysisService.getYoutubeVideos(); + + assertThat(res).hasSize(1); + } + } + + @Nested + @DisplayName("getNaverProducts()는") + class NaverProductsTest { + + @Test + @DisplayName("피부관리 키워드로 상품 3개 검색한다.") + void success() { + NaverProductDto product = new NaverProductDto( + "1", + "상품", + "url", + 1000, + "img", + "category", + "mall", + "brand" + ); + + given(naverClient.searchProducts("피부관리", 3)) + .willReturn(List.of(product)); + + List res = analysisService.getNaverProducts(); + + assertThat(res).hasSize(1); + } + } +} + diff --git a/api-module/src/test/java/hongik/triple/apimodule/member/MemberServiceTest.java b/api-module/src/test/java/hongik/triple/apimodule/member/MemberServiceTest.java index 53f9adb..540ce5c 100644 --- a/api-module/src/test/java/hongik/triple/apimodule/member/MemberServiceTest.java +++ b/api-module/src/test/java/hongik/triple/apimodule/member/MemberServiceTest.java @@ -3,6 +3,7 @@ import hongik.triple.apimodule.application.member.MemberService; import hongik.triple.apimodule.global.security.jwt.TokenProvider; import hongik.triple.apimodule.global.security.jwt.TokenDto; +import hongik.triple.commonmodule.dto.member.MemberReq; import hongik.triple.commonmodule.dto.member.MemberRes; import hongik.triple.commonmodule.enumerate.MemberType; import hongik.triple.domainmodule.domain.member.Member; @@ -350,29 +351,29 @@ void success() { } } - // TODO: updateProfile() 메서드에 skin_type 필드 추가 후 테스트 케이스 수정 -// @Nested -// @DisplayName("updateProfile()은") -// class UpdateProfileTest { -// -// @Test -// @DisplayName("회원 정보를 정상적으로 수정한다.") -// void success() { -// // given -// Member member = new Member("oldName", "email@test.com", MemberType.KAKAO); -// MemberReq req = new MemberReq("newName", "OILY"); -// -// given(memberRepository.save(any(Member.class))) -// .willReturn(member); -// -// // when -// MemberRes result = memberService.updateProfile(member, req); -// -// // then -// assertThat(result.name()).isEqualTo("newName"); -// verify(memberRepository, times(1)).save(member); -// } -// } + @Nested + @DisplayName("updateProfile()은") + class UpdateProfileTest { + + @Test + @DisplayName("회원 정보를 정상적으로 수정한다.") + void success() { + // given + Member member = new Member("oldName", "email@test.com", MemberType.KAKAO); + MemberReq req = new MemberReq("newName", "OILY"); + + given(memberRepository.save(any(Member.class))) + .willReturn(member); + + // when + MemberRes result = memberService.updateProfile(member, req); + + // then + assertThat(result.name()).isEqualTo("newName"); + assertThat(result.skinType()).isEqualTo("OILY"); + verify(memberRepository, times(1)).save(member); + } + } @Nested @DisplayName("withdrawal()은") diff --git a/api-module/src/test/java/hongik/triple/apimodule/survey/SurveyServiceTest.java b/api-module/src/test/java/hongik/triple/apimodule/survey/SurveyServiceTest.java new file mode 100644 index 0000000..30254fa --- /dev/null +++ b/api-module/src/test/java/hongik/triple/apimodule/survey/SurveyServiceTest.java @@ -0,0 +1,249 @@ +package hongik.triple.apimodule.survey; + +import hongik.triple.apimodule.application.survey.SurveyService; +import hongik.triple.commonmodule.dto.survey.SurveyReq; +import hongik.triple.commonmodule.dto.survey.SurveyRes; +import hongik.triple.commonmodule.enumerate.MemberType; +import hongik.triple.commonmodule.enumerate.SkinType; +import hongik.triple.domainmodule.domain.member.Member; +import hongik.triple.domainmodule.domain.member.repository.MemberRepository; +import hongik.triple.domainmodule.domain.survey.Survey; +import hongik.triple.domainmodule.domain.survey.repository.SurveyRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.*; + +@DisplayName("SurveyService 테스트") +@ExtendWith(MockitoExtension.class) +class SurveyServiceTest { + + @Mock + private SurveyRepository surveyRepository; + + @Mock + private MemberRepository memberRepository; + + @InjectMocks + private SurveyService surveyService; + + private Member member; + + @BeforeEach + void setup() { + member = new Member("user", "email@test.com", MemberType.KAKAO); + ReflectionTestUtils.setField(member, "memberId", 1L); + } + + private Map buildValidAnswers() { + Map answers = new HashMap<>(); + for (int i = 1; i <= 12; i++) { + answers.put(String.format("Q%03d", i), 3); // 모든 값을 3으로 설정 + } + return answers; + } + + @Nested + @DisplayName("registerSurvey()는") + class RegisterSurveyTest { + + @Test + @DisplayName("유효한 설문 응답을 저장하고 SurveyRes를 반환한다.") + void success() { + // given + Map answers = buildValidAnswers(); + SurveyReq req = new SurveyReq(answers); + + Survey survey = Survey.builder() + .member(member) + .body(answers) + .skinType(SkinType.COMBINATION) + .build(); + ReflectionTestUtils.setField(survey, "surveyId", 10L); + + given(memberRepository.findById(1L)).willReturn(Optional.of(member)); + given(surveyRepository.save(any(Survey.class))).willReturn(survey); + + // when + SurveyRes result = surveyService.registerSurvey(member, req); + + // then + assertThat(result.surveyId()).isEqualTo(10L); + assertThat(result.memberId()).isEqualTo(1L); + assertThat(result.skinType()).isEqualTo(SkinType.COMBINATION.getDescription()); + assertThat(result.questions()).hasSize(12); + assertThat(result.totalScore()).isGreaterThan(0); + + verify(surveyRepository, times(1)).save(any()); + } + + @Test + @DisplayName("사용자를 찾을 수 없으면 예외를 던진다.") + void memberNotFound() { + // given + SurveyReq req = new SurveyReq(buildValidAnswers()); + given(memberRepository.findById(1L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> surveyService.registerSurvey(member, req)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("존재하지 않는 사용자"); + + verify(surveyRepository, never()).save(any()); + } + + @Test + @DisplayName("필수 답변이 빠져 있으면 예외를 던진다.") + void missingRequiredQuestions() { + // given + Map answers = buildValidAnswers(); + answers.remove("Q001"); // 필수 항목 제거 + + SurveyReq req = new SurveyReq(answers); + + given(memberRepository.findById(1L)).willReturn(Optional.of(member)); + + // when & then + assertThatThrownBy(() -> surveyService.registerSurvey(member, req)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("필수 질문"); + + verify(surveyRepository, never()).save(any()); + } + + @Test + @DisplayName("점수가 1~5 범위를 벗어나면 예외를 던진다.") + void invalidScore() { + Map answers = buildValidAnswers(); + answers.put("Q005", 99); // invalid + + SurveyReq req = new SurveyReq(answers); + + given(memberRepository.findById(1L)).willReturn(Optional.of(member)); + + assertThatThrownBy(() -> surveyService.registerSurvey(member, req)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("점수는 1-5 범위"); + + verify(surveyRepository, never()).save(any()); + } + } + + @Nested + @DisplayName("getSurveyQuestions()는") + class GetSurveyQuestionsTest { + + @Test + @DisplayName("설문 질문 목록을 반환한다.") + void success() { + // when + SurveyRes result = surveyService.getSurveyQuestions(); + + // then + assertThat(result.questions()).isNotEmpty(); + assertThat(result.questions()).hasSize(12); + } + } + + @Nested + @DisplayName("getSurveyList()는") + class GetSurveyListTest { + + @Test + @DisplayName("회원 ID가 있을 경우 해당 회원의 설문 목록을 반환한다.") + void successMemberCase() { + Pageable pageable = PageRequest.of(0, 10); + Survey survey = Survey.builder() + .member(member) + .body(buildValidAnswers()) + .skinType(SkinType.OILY) + .build(); + + Page page = new PageImpl<>(List.of(survey), pageable, 1); + + given(surveyRepository.findByMember_MemberIdOrderByCreatedAtDesc(1L, pageable)) + .willReturn(page); + + Page result = surveyService.getSurveyList(member, pageable); + + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).skinType()).isEqualTo(SkinType.OILY.getDescription()); + } + + @Test + @DisplayName("회원 ID가 null이면 전체 목록을 반환한다.") + void successAllCase() { + Member noIdMember = new Member("user", "email", MemberType.KAKAO); + Pageable pageable = PageRequest.of(0, 10); + + Survey survey = Survey.builder() + .member(member) + .body(buildValidAnswers()) + .skinType(SkinType.DRY) + .build(); + + Page page = new PageImpl<>(List.of(survey), pageable, 1); + + given(surveyRepository.findAllByOrderByCreatedAtDesc(pageable)) + .willReturn(page); + + Page result = surveyService.getSurveyList(noIdMember, pageable); + + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).skinType()).isEqualTo(SkinType.DRY.getDescription()); + } + } + + @Nested + @DisplayName("getSurveyDetail()은") + class GetSurveyDetailTest { + + @Test + @DisplayName("설문 상세 정보를 반환한다.") + void success() { + Survey survey = Survey.builder() + .member(member) + .body(buildValidAnswers()) + .skinType(SkinType.COMBINATION) + .build(); + ReflectionTestUtils.setField(survey, "surveyId", 99L); + + given(surveyRepository.findById(99L)).willReturn(Optional.of(survey)); + + SurveyRes result = surveyService.getSurveyDetail(99L); + + assertThat(result.surveyId()).isEqualTo(99L); + assertThat(result.skinType()).isEqualTo(SkinType.COMBINATION.getDescription()); + } + + @Test + @DisplayName("설문이 존재하지 않으면 예외를 던진다.") + void notFound() { + given(surveyRepository.findById(10L)).willReturn(Optional.empty()); + + assertThatThrownBy(() -> surveyService.getSurveyDetail(10L)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("존재하지 않는 설문조사"); + } + } +} diff --git a/domain-module/src/main/java/hongik/triple/domainmodule/domain/member/Member.java b/domain-module/src/main/java/hongik/triple/domainmodule/domain/member/Member.java index bcbfdd9..048760b 100644 --- a/domain-module/src/main/java/hongik/triple/domainmodule/domain/member/Member.java +++ b/domain-module/src/main/java/hongik/triple/domainmodule/domain/member/Member.java @@ -35,6 +35,10 @@ public void updateSkinType(String skinType) { this.skinType = skinType; } + public void updateName(String name) { + this.name = name; + } + public Member(String name, String email, MemberType memberType) { this.name = name; this.email = email;