diff --git a/src/test/java/com/jobnote/domain/applicationform/service/ApplicationFormServiceTest.java b/src/test/java/com/jobnote/domain/applicationform/service/ApplicationFormServiceTest.java index 2d3ba70..db4d238 100644 --- a/src/test/java/com/jobnote/domain/applicationform/service/ApplicationFormServiceTest.java +++ b/src/test/java/com/jobnote/domain/applicationform/service/ApplicationFormServiceTest.java @@ -12,6 +12,7 @@ import com.jobnote.domain.document.service.DocumentService; import com.jobnote.domain.schedule.dto.ScheduleResponse; import com.jobnote.domain.schedule.service.ScheduleService; +import com.jobnote.domain.user.domain.UserFixture; import com.jobnote.global.exception.JobNoteException; import com.jobnote.domain.user.domain.User; import com.jobnote.domain.user.service.UserService; @@ -40,31 +41,19 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.*; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; class ApplicationFormServiceTest extends ServiceUnitTest { - @InjectMocks - private ApplicationFormService applicationFormService; + @InjectMocks private ApplicationFormService applicationFormService; - @Mock - private UserService userService; + @Mock private UserService userService; + @Mock private ScheduleService scheduleService; + @Mock private DocumentService documentService; + @Mock private ApplicationFormDocumentService applicationFormDocumentService; - @Mock - private ScheduleService scheduleService; - - @Mock - private DocumentService documentService; - - @Mock - private ApplicationFormDocumentService applicationFormDocumentService; - - @Mock - private ApplicationFormRepository applicationFormRepository; - - @Mock - private ApplicationFormDocumentRepository applicationFormDocumentRepository; + @Mock private ApplicationFormRepository applicationFormRepository; + @Mock private ApplicationFormDocumentRepository applicationFormDocumentRepository; private User user; private ApplicationForm applicationForm; @@ -74,115 +63,129 @@ class ApplicationFormServiceTest extends ServiceUnitTest { @BeforeEach void setUp() { - user = mock(User.class); - applicationForm = mock(ApplicationForm.class); + user = UserFixture.createMember(userId, "test@gmail.com", "1234", "test"); + applicationForm = ApplicationForm.builder() + .user(user) + .companyName("네이버") + .companyAddress("경기도 성남시 분당구") + .companyUrl("https://naver.com") + .companyScale("대기업") + .position("백엔드 개발자") + .status(APPLIED) + .build(); } - @DisplayName("지원서 목록을 전체 조회한다.") - @Test - void getAllApplicationForms() { - // given - ApplicationForm form1 = ApplicationForm.builder().user(user).companyName("네이버").status(APPLIED).build(); - ApplicationForm form2 = ApplicationForm.builder().user(user).companyName("카카오").status(DOCUMENT_PASSED).build(); - List expectedResult = Arrays.asList(form1, form2); + @Nested + @DisplayName("지원서 다건 조회") + class GetAll { - Page pageExpectedResult = new PageImpl<>(expectedResult); - given(applicationFormRepository.findAllByUserId(eq(userId), any(Pageable.class))).willReturn(pageExpectedResult); + @DisplayName("지원서 목록을 전체 조회한다.") + @Test + void getAllApplicationForms() { + // given + ApplicationForm form1 = ApplicationForm.builder().user(user).companyName("네이버").status(APPLIED).build(); + ApplicationForm form2 = ApplicationForm.builder().user(user).companyName("카카오").status(DOCUMENT_PASSED).build(); + List expectedResult = Arrays.asList(form1, form2); - // when - Page pageActualResult = applicationFormService.getAll(userId, Pageable.unpaged()); - List actualResult = pageActualResult.getContent(); + Page pageExpectedResult = new PageImpl<>(expectedResult); + given(applicationFormRepository.findAllByUserId(eq(userId), any(Pageable.class))).willReturn(pageExpectedResult); - // then - assertThat(actualResult).hasSize(2); - assertThat(actualResult.get(0).companyName()).isEqualTo("네이버"); - then(applicationFormRepository).should().findAllByUserId(userId, Pageable.unpaged()); - } + // when + Page pageActualResult = applicationFormService.getAll(userId, Pageable.unpaged()); + List actualResult = pageActualResult.getContent(); - @Test - @DisplayName("지원서마다 일정들이 그룹핑되어 함께 반환된다") - void getAllApplicationForms_withSchedules() { - // given - ApplicationForm form1 = ApplicationForm.builder().user(user).companyName("네이버").status(APPLIED).build(); - ApplicationForm form2 = ApplicationForm.builder().user(user).companyName("카카오").status(DOCUMENT_PASSED).build(); - ReflectionTestUtils.setField(form1, "id", 1L); - ReflectionTestUtils.setField(form2, "id", 2L); - List forms = List.of(form1, form2); + // then + assertThat(actualResult).hasSize(2); + assertThat(actualResult.get(0).companyName()).isEqualTo("네이버"); + then(applicationFormRepository).should().findAllByUserId(userId, Pageable.unpaged()); + } - Page pageForms = new PageImpl<>(forms); - given(applicationFormRepository.findAllByUserId(eq(userId), any(Pageable.class))).willReturn(pageForms); + @Test + @DisplayName("지원서마다 일정들이 그룹핑되어 함께 반환된다") + void getAllApplicationForms_withSchedules() { + // given + ApplicationForm form1 = ApplicationForm.builder().user(user).companyName("네이버").status(APPLIED).build(); + ApplicationForm form2 = ApplicationForm.builder().user(user).companyName("카카오").status(DOCUMENT_PASSED).build(); + ReflectionTestUtils.setField(form1, "id", 1L); + ReflectionTestUtils.setField(form2, "id", 2L); + List forms = List.of(form1, form2); - ScheduleResponse schedule1 = new ScheduleResponse(101L, "지원서 제출", "오전", LocalDateTime.of(2025, 8, 1, 10, 0), PLANNED); - ScheduleResponse schedule2 = new ScheduleResponse(102L, "코딩테스트", "연습문제 풀이", LocalDateTime.of(2025, 8, 2, 9, 0), PLANNED); - ScheduleResponse schedule3 = new ScheduleResponse(103L, "2차 면접", "예상질문지 복습", LocalDateTime.of(2025, 8, 5, 9, 0), PLANNED); + Page pageForms = new PageImpl<>(forms); + given(applicationFormRepository.findAllByUserId(eq(userId), any(Pageable.class))).willReturn(pageForms); - given(scheduleService.getAllGroupedByApplicationFormIds(eq(userId), eq(List.of(1L, 2L)))) - .willReturn(Map.of( - 1L, List.of(schedule1), - 2L, List.of(schedule2, schedule3) - )); + ScheduleResponse schedule1 = new ScheduleResponse(101L, "지원서 제출", "오전", LocalDateTime.of(2025, 8, 1, 10, 0), PLANNED); + ScheduleResponse schedule2 = new ScheduleResponse(102L, "코딩테스트", "연습문제 풀이", LocalDateTime.of(2025, 8, 2, 9, 0), PLANNED); + ScheduleResponse schedule3 = new ScheduleResponse(103L, "2차 면접", "예상질문지 복습", LocalDateTime.of(2025, 8, 5, 9, 0), PLANNED); - // when - Page actualResult = applicationFormService.getAll(userId, Pageable.unpaged()); + given(scheduleService.getAllGroupedByApplicationFormIds(eq(userId), eq(List.of(1L, 2L)))) + .willReturn(Map.of( + 1L, List.of(schedule1), + 2L, List.of(schedule2, schedule3) + )); - // then - assertThat(actualResult).hasSize(2); + // when + Page actualResult = applicationFormService.getAll(userId, Pageable.unpaged()); - ApplicationFormResponse result1 = actualResult.getContent().get(0); - assertThat(result1.companyName()).isEqualTo("네이버"); - assertThat(result1.schedules()).hasSize(1); - assertThat(result1.schedules().get(0).title()).isEqualTo("지원서 제출"); + // then + assertThat(actualResult).hasSize(2); - ApplicationFormResponse result2 = actualResult.getContent().get(1); - assertThat(result2.companyName()).isEqualTo("카카오"); - assertThat(result2.schedules()).hasSize(2); - assertThat(result2.schedules().get(0).title()).isEqualTo("코딩테스트"); + ApplicationFormResponse result1 = actualResult.getContent().get(0); + assertThat(result1.companyName()).isEqualTo("네이버"); + assertThat(result1.schedules()).hasSize(1); + assertThat(result1.schedules().get(0).title()).isEqualTo("지원서 제출"); - then(applicationFormRepository).should().findAllByUserId(eq(userId), any(Pageable.class)); - then(scheduleService).should().getAllGroupedByApplicationFormIds(eq(userId), eq(List.of(1L, 2L))); - } + ApplicationFormResponse result2 = actualResult.getContent().get(1); + assertThat(result2.companyName()).isEqualTo("카카오"); + assertThat(result2.schedules()).hasSize(2); + assertThat(result2.schedules().get(0).title()).isEqualTo("코딩테스트"); - @Test - @DisplayName("지원서마다 문서들이 그룹핑되어 함께 반환된다") - void getAllApplicationForms_withDocuments() { - // given - ApplicationForm form1 = ApplicationForm.builder().user(user).companyName("네이버").status(APPLIED).build(); - ApplicationForm form2 = ApplicationForm.builder().user(user).companyName("카카오").status(DOCUMENT_PASSED).build(); - ReflectionTestUtils.setField(form1, "id", 1L); - ReflectionTestUtils.setField(form2, "id", 2L); - List forms = List.of(form1, form2); + then(applicationFormRepository).should().findAllByUserId(eq(userId), any(Pageable.class)); + then(scheduleService).should().getAllGroupedByApplicationFormIds(eq(userId), eq(List.of(1L, 2L))); + } - Page pageForms = new PageImpl<>(forms); - given(applicationFormRepository.findAllByUserId(eq(userId), any(Pageable.class))).willReturn(pageForms); + @Test + @DisplayName("지원서마다 문서들이 그룹핑되어 함께 반환된다") + void getAllApplicationForms_withDocuments() { + // given + ApplicationForm form1 = ApplicationForm.builder().user(user).companyName("네이버").status(APPLIED).build(); + ApplicationForm form2 = ApplicationForm.builder().user(user).companyName("카카오").status(DOCUMENT_PASSED).build(); + ReflectionTestUtils.setField(form1, "id", 1L); + ReflectionTestUtils.setField(form2, "id", 2L); + List forms = List.of(form1, form2); - DocumentSimpleResponse document1 = new DocumentSimpleResponse(101L, DocumentType.RESUME, "네이버 이력서"); - DocumentSimpleResponse document2 = new DocumentSimpleResponse(102L, DocumentType.COVER_LETTER, "네이버 자소서"); - DocumentSimpleResponse document3 = new DocumentSimpleResponse(103L, DocumentType.RESUME, "카카오 이력서"); + Page pageForms = new PageImpl<>(forms); + given(applicationFormRepository.findAllByUserId(eq(userId), any(Pageable.class))).willReturn(pageForms); - given(applicationFormDocumentService.getAllSimpleGroupedByApplicationFormIds(eq(userId), eq(List.of(1L, 2L)))) - .willReturn(Map.of( - 1L, List.of(document1, document2), - 2L, List.of(document3) - )); + DocumentSimpleResponse document1 = new DocumentSimpleResponse(101L, DocumentType.RESUME, "네이버 이력서"); + DocumentSimpleResponse document2 = new DocumentSimpleResponse(102L, DocumentType.COVER_LETTER, "네이버 자소서"); + DocumentSimpleResponse document3 = new DocumentSimpleResponse(103L, DocumentType.RESUME, "카카오 이력서"); - // when - Page actualResult = applicationFormService.getAll(userId, Pageable.unpaged()); + given(applicationFormDocumentService.getAllSimpleGroupedByApplicationFormIds(eq(userId), eq(List.of(1L, 2L)))) + .willReturn(Map.of( + 1L, List.of(document1, document2), + 2L, List.of(document3) + )); - // then - assertThat(actualResult).hasSize(2); + // when + Page actualResult = applicationFormService.getAll(userId, Pageable.unpaged()); + + // then + assertThat(actualResult).hasSize(2); + + ApplicationFormResponse result1 = actualResult.getContent().get(0); + assertThat(result1.companyName()).isEqualTo("네이버"); + assertThat(result1.documents()).hasSize(2); + assertThat(result1.documents().get(0).title()).isEqualTo("네이버 이력서"); - ApplicationFormResponse result1 = actualResult.getContent().get(0); - assertThat(result1.companyName()).isEqualTo("네이버"); - assertThat(result1.documents()).hasSize(2); - assertThat(result1.documents().get(0).title()).isEqualTo("네이버 이력서"); + ApplicationFormResponse result2 = actualResult.getContent().get(1); + assertThat(result2.companyName()).isEqualTo("카카오"); + assertThat(result2.documents()).hasSize(1); + assertThat(result2.documents().get(0).title()).isEqualTo("카카오 이력서"); - ApplicationFormResponse result2 = actualResult.getContent().get(1); - assertThat(result2.companyName()).isEqualTo("카카오"); - assertThat(result2.documents()).hasSize(1); - assertThat(result2.documents().get(0).title()).isEqualTo("카카오 이력서"); + then(applicationFormRepository).should().findAllByUserId(eq(userId), any(Pageable.class)); + then(applicationFormDocumentService).should().getAllSimpleGroupedByApplicationFormIds(eq(userId), eq(List.of(1L, 2L))); + } - then(applicationFormRepository).should().findAllByUserId(eq(userId), any(Pageable.class)); - then(applicationFormDocumentService).should().getAllSimpleGroupedByApplicationFormIds(eq(userId), eq(List.of(1L, 2L))); } @Nested @@ -193,7 +196,6 @@ class GetById { void success() { // given given(applicationFormRepository.findById(formId)).willReturn(Optional.of(applicationForm)); - willDoNothing().given(applicationForm).validateOwner(userId); // when applicationFormService.getById(userId, formId); @@ -219,12 +221,9 @@ void fail_notFound() { void fail_forbidden() { // given given(applicationFormRepository.findById(formId)).willReturn(Optional.of(applicationForm)); - doThrow(new JobNoteException(FORBIDDEN)) - .when(applicationForm) - .validateOwner(userId); // when & then - assertThatThrownBy(() -> applicationFormService.getById(userId, formId)) + assertThatThrownBy(() -> applicationFormService.getById(2L, formId)) .isInstanceOf(JobNoteException.class) .hasMessage(FORBIDDEN.getMessage()); } @@ -246,7 +245,7 @@ void saveApplicationForm() { } @Nested - @DisplayName("지원서 수정") + @DisplayName("지원서 업데이트") class Update { ApplicationFormRequest request = new ApplicationFormRequest("카카오", "경기도 성남시", null, null, null, APPLIED, null, null); @@ -255,14 +254,13 @@ class Update { void success() { // given given(applicationFormRepository.findById(formId)).willReturn(Optional.of(applicationForm)); - willDoNothing().given(applicationForm).validateOwner(userId); // when applicationFormService.update(userId, formId, request); // then - then(applicationFormRepository).should().findById(formId); - then(applicationForm).should().update(request); + ApplicationFormResponse form = applicationFormService.getById(userId, formId); + assertThat(form.companyName()).isEqualTo("카카오"); } @DisplayName("실패 - 지원서를 찾을 수 없음") @@ -275,7 +273,6 @@ void fail_notFound() { assertThatThrownBy(() -> applicationFormService.update(userId, formId, request)) .isInstanceOf(JobNoteException.class) .hasMessage(NOT_FOUND_APPLICATION_FORM.getMessage()); - then(applicationForm).should(never()).update(request); } @DisplayName("실패 - 권한 없음") @@ -283,15 +280,11 @@ void fail_notFound() { void fail_forbidden() { // given given(applicationFormRepository.findById(formId)).willReturn(Optional.of(applicationForm)); - doThrow(new JobNoteException(FORBIDDEN)) - .when(applicationForm) - .validateOwner(userId); // when & then - assertThatThrownBy(() -> applicationFormService.update(userId, formId, request)) + assertThatThrownBy(() -> applicationFormService.update(2L, formId, request)) .isInstanceOf(JobNoteException.class) .hasMessage(FORBIDDEN.getMessage()); - then(applicationForm).should(never()).update(request); } } @@ -303,7 +296,6 @@ class Delete { void success() { // given given(applicationFormRepository.findById(formId)).willReturn(Optional.of(applicationForm)); - willDoNothing().given(applicationForm).validateOwner(userId); // when applicationFormService.delete(userId, formId); @@ -330,12 +322,9 @@ void fail_notFound() { void fail_forbidden() { // given given(applicationFormRepository.findById(formId)).willReturn(Optional.of(applicationForm)); - doThrow(new JobNoteException(FORBIDDEN)) - .when(applicationForm) - .validateOwner(userId); // when & then - assertThatThrownBy(() -> applicationFormService.delete(userId, formId)) + assertThatThrownBy(() -> applicationFormService.delete(2L, formId)) .isInstanceOf(JobNoteException.class) .hasMessage(FORBIDDEN.getMessage()); then(applicationFormRepository).should(never()).delete(any(ApplicationForm.class)); diff --git a/src/test/java/com/jobnote/domain/applicationformdocument/service/ApplicationFormDocumentServiceTest.java b/src/test/java/com/jobnote/domain/applicationformdocument/service/ApplicationFormDocumentServiceTest.java new file mode 100644 index 0000000..a37b613 --- /dev/null +++ b/src/test/java/com/jobnote/domain/applicationformdocument/service/ApplicationFormDocumentServiceTest.java @@ -0,0 +1,153 @@ +package com.jobnote.domain.applicationformdocument.service; + +import com.jobnote.ServiceUnitTest; +import com.jobnote.domain.applicationform.domain.ApplicationForm; +import com.jobnote.domain.applicationform.dto.ApplicationFormSimpleResponse; +import com.jobnote.domain.applicationformdocument.domain.ApplicationFormDocument; +import com.jobnote.domain.applicationformdocument.dto.ApplicationFormDocumentRequest; +import com.jobnote.domain.applicationformdocument.repository.ApplicationFormDocumentRepository; +import com.jobnote.domain.document.domain.Document; +import com.jobnote.domain.document.dto.DocumentSimpleResponse; +import com.jobnote.domain.document.repository.DocumentRepository; +import com.jobnote.domain.user.domain.User; +import com.jobnote.domain.user.domain.UserFixture; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static com.jobnote.domain.applicationform.domain.ApplicationFormStatus.APPLIED; +import static com.jobnote.domain.applicationform.domain.ApplicationFormStatus.PLANNED; +import static com.jobnote.domain.document.domain.DocumentType.RESUME; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; + +class ApplicationFormDocumentServiceTest extends ServiceUnitTest { + + @InjectMocks private ApplicationFormDocumentService applicationFormDocumentService; + + @Mock private ApplicationFormDocumentRepository applicationFormDocumentRepository; + @Mock private DocumentRepository documentRepository; + + private ApplicationForm applicationForm; + private ApplicationForm applicationForm2; + private Document document; + private Document document2; + + private final Long userId = 1L; + private final Long formId = 1L; + private final Long formId2 = 2L; + private final Long documentId = 1L; + private final Long documentId2 = 2L; + + @BeforeEach + void setUp() { + User user = UserFixture.createMember(userId, "test@gmail.com", "1234", "test"); + applicationForm = ApplicationForm.builder() + .user(user) + .companyName("네이버") + .companyAddress("경기도 성남시 분당구") + .companyUrl("https://naver.com") + .companyScale("대기업") + .position("백엔드 개발자") + .status(APPLIED) + .build(); + applicationForm2 = ApplicationForm.builder() + .user(user) + .companyName("카카오") + .companyAddress("경기도 성남시 분당구") + .companyUrl("https://kakao.com") + .companyScale("대기업") + .position("프론트엔드 개발자") + .status(PLANNED) + .build(); + document = Document.builder() + .user(user) + .documentType(RESUME) + .title("네이버 이력서") + .build(); + document2 = Document.builder() + .user(user) + .documentType(RESUME) + .title("카카오 이력서") + .build(); + ReflectionTestUtils.setField(applicationForm, "id", formId); + ReflectionTestUtils.setField(applicationForm2, "id", formId2); + ReflectionTestUtils.setField(document, "id", documentId); + ReflectionTestUtils.setField(document2, "id", documentId2); + } + + @DisplayName("지원서-문서 연결 등록 및 조회") + @Test + void connectDocumentToApplicationForm() { + // given + List afds = List.of( + ApplicationFormDocument.builder().applicationForm(applicationForm).document(document).build(), + ApplicationFormDocument.builder().applicationForm(applicationForm).document(document2).build() + ); + + given(documentRepository.findById(1L)).willReturn(Optional.of(document)); + given(documentRepository.findById(2L)).willReturn(Optional.of(document2)); + given(applicationFormDocumentRepository.findAllByUserIdAndApplicationFormIdIn(eq(userId), eq(List.of(formId)))).willReturn(afds); + + // when + List requests = List.of( + new ApplicationFormDocumentRequest(1L), + new ApplicationFormDocumentRequest(2L) + ); + applicationFormDocumentService.saveAll(userId, applicationForm, requests); + + // then + List actualResult = applicationFormDocumentService.getAllByApplicationFormId(userId, formId); + assertThat(actualResult).hasSize(2); + assertThat(actualResult.get(0).getDocument().getTitle()).isEqualTo("네이버 이력서"); + } + + @DisplayName("문서 기준으로 연결된 지원서 목록 조회") + @Test + void getAllSimpleByDocumentId() { + ApplicationFormDocument afd = ApplicationFormDocument.builder().applicationForm(applicationForm).document(document).build(); + given(applicationFormDocumentRepository.findAllByUserIdAndDocumentId(userId, documentId)).willReturn(List.of(afd)); + + List actualResult = applicationFormDocumentService.getAllSimpleByDocumentId(userId, documentId); + + assertThat(actualResult).hasSize(1); + assertThat(actualResult.get(0).companyName()).isEqualTo("네이버"); + } + + @DisplayName("지원서 기준으로 연결된 문서 목록 조회") + @Test + void getAllSimpleByApplicationFormId() { + ApplicationFormDocument afd = ApplicationFormDocument.builder().applicationForm(applicationForm).document(document).build(); + ApplicationFormDocument afd2 = ApplicationFormDocument.builder().applicationForm(applicationForm).document(document2).build(); + given(applicationFormDocumentRepository.findAllByUserIdAndApplicationFormIdIn(userId, List.of(formId))).willReturn(List.of(afd, afd2)); + + List actualResult = applicationFormDocumentService.getAllSimpleByApplicationFormId(userId, formId); + + assertThat(actualResult).hasSize(2); + assertThat(actualResult.get(0).title()).isEqualTo("네이버 이력서"); + assertThat(actualResult.get(1).title()).isEqualTo("카카오 이력서"); + } + + @DisplayName("지원서 ID별 문서 목록 그룹핑 조회") + @Test + void getAllSimpleGroupedByApplicationFormIds() { + ApplicationFormDocument afd = ApplicationFormDocument.builder().applicationForm(applicationForm).document(document).build(); + ApplicationFormDocument afd2 = ApplicationFormDocument.builder().applicationForm(applicationForm2).document(document2).build(); + given(applicationFormDocumentRepository.findAllByUserIdAndApplicationFormIdIn(userId, List.of(formId, formId2))).willReturn(List.of(afd, afd2)); + + Map> actualResult = applicationFormDocumentService.getAllSimpleGroupedByApplicationFormIds(userId, List.of(formId, formId2)); + + assertThat(actualResult.get(formId)).hasSize(1); + assertThat(actualResult.get(formId).get(0).title()).isEqualTo("네이버 이력서"); + assertThat(actualResult.get(formId2)).hasSize(1); + assertThat(actualResult.get(formId2).get(0).title()).isEqualTo("카카오 이력서"); + } +} diff --git a/src/test/java/com/jobnote/domain/document/service/DocumentServiceTest.java b/src/test/java/com/jobnote/domain/document/service/DocumentServiceTest.java new file mode 100644 index 0000000..cd74c29 --- /dev/null +++ b/src/test/java/com/jobnote/domain/document/service/DocumentServiceTest.java @@ -0,0 +1,186 @@ +package com.jobnote.domain.document.service; + +import com.jobnote.ServiceUnitTest; +import com.jobnote.domain.applicationform.dto.ApplicationFormSimpleResponse; +import com.jobnote.domain.applicationformdocument.service.ApplicationFormDocumentService; +import com.jobnote.domain.document.domain.Document; +import com.jobnote.domain.document.domain.DocumentType; +import com.jobnote.domain.document.domain.DocumentVersion; +import com.jobnote.domain.document.dto.DocumentRequest; +import com.jobnote.domain.document.dto.DocumentResponse; +import com.jobnote.domain.document.dto.DocumentVersionResponse; +import com.jobnote.domain.document.repository.DocumentRepository; +import com.jobnote.domain.document.repository.DocumentVersionRepository; +import com.jobnote.domain.user.domain.User; +import com.jobnote.domain.user.domain.UserFixture; +import com.jobnote.domain.user.service.UserService; +import com.jobnote.global.exception.JobNoteException; +import com.jobnote.infra.s3.service.S3Service; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.test.util.ReflectionTestUtils; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static com.jobnote.domain.applicationform.domain.ApplicationFormStatus.APPLIED; +import static com.jobnote.domain.document.domain.DocumentType.RESUME; +import static com.jobnote.global.common.ResponseCode.FORBIDDEN; +import static com.jobnote.global.common.ResponseCode.NOT_FOUND_DOCUMENT; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.*; + +class DocumentServiceTest extends ServiceUnitTest { + + @InjectMocks private DocumentService documentService; + + @Mock private DocumentRepository documentRepository; + @Mock private DocumentVersionRepository documentVersionRepository; + @Mock private UserService userService; + @Mock private S3Service s3Service; + @Mock private ApplicationFormDocumentService applicationFormDocumentService; + + private User user; + private Document document; + private DocumentVersion documentVersion; + + private final Long userId = 1L; + private final Long documentId = 1L; + + @BeforeEach + void setUp() { + user = UserFixture.createMember(userId, "test@gmail.com", "1234", "test"); + + document = Document.builder() + .user(user) + .documentType(RESUME) + .title("네이버 이력서") + .build(); + ReflectionTestUtils.setField(document, "id", documentId); + ReflectionTestUtils.setField(document, "modifiedDate", LocalDateTime.now()); + + documentVersion = DocumentVersion.builder() + .document(document) + .version(1) + .title("네이버 이력서") + .originFileName("naver_resume.pdf") + .fileKey("1/naver_resume.pdf") + .fileSize(1024L) + .build(); + ReflectionTestUtils.setField(documentVersion, "id", 10L); + ReflectionTestUtils.setField(documentVersion, "createdDate", LocalDateTime.now()); + } + + @DisplayName("새로운 문서 업로드") + @Test + void uploadNewDocument() { + DocumentRequest request = new DocumentRequest( + "네이버 이력서", + "naver_resume.pdf", + "1/naver_resume.pdf", + RESUME, + 1024L + ); + + given(userService.getUserById(userId)).willReturn(user); + given(documentRepository.save(any(Document.class))).willReturn(document); + willDoNothing().given(s3Service).validateIsOwner(userId, request.fileKey()); + given(s3Service.getFileSize(request.fileKey())).willReturn(1024L); + given(documentVersionRepository.save(any(DocumentVersion.class))).willReturn(documentVersion); + + documentService.uploadNewDocument(userId, request); + + then(documentRepository).should().save(any(Document.class)); + then(documentVersionRepository).should().save(any(DocumentVersion.class)); + } + + @DisplayName("존재하지 않는 문서 ID로 버전 업로드 시 예외 발생") + @Test + void uploadNewVersionDocument_notFound() { + DocumentRequest request = new DocumentRequest( + "카카오 이력서", + "kakao_resume.pdf", + "1/kakao_resume.pdf", + RESUME, + 2048L + ); + given(documentRepository.findById(documentId)).willReturn(Optional.empty()); + + assertThatThrownBy(() -> documentService.uploadNewVersionDocument(userId, documentId, request)) + .isInstanceOf(JobNoteException.class) + .hasMessageContaining(NOT_FOUND_DOCUMENT.getMessage()); + } + + @DisplayName("문서 전체 조회시 최신 버전과 연결된 지원서 정보를 포함한다") + @Test + void getAll() { + Page page = new PageImpl<>(List.of(document)); + + given(documentRepository.findAllByUserId(eq(userId), any(Pageable.class))).willReturn(page); + given(documentVersionRepository.findLatestVersionByDocumentId(documentId)).willReturn(1); + given(applicationFormDocumentService.getAllSimpleByDocumentId(userId, documentId)) + .willReturn(List.of(new ApplicationFormSimpleResponse(document.getId(), "네이버", "백엔드", APPLIED))); + + Page actual = documentService.getAll(userId, Pageable.unpaged()); + + assertThat(actual.getContent()).hasSize(1); + assertThat(actual.getContent().get(0).title()).isEqualTo("네이버 이력서"); + } + + @DisplayName("문서 버전 전체 조회 시 S3 파일 URL을 포함한다") + @Test + void getAllVersions() { + Page page = new PageImpl<>(List.of(documentVersion)); + + given(documentVersionRepository.findAllByUserIdAndDocumentId(eq(userId), eq(documentId), any(Pageable.class))) + .willReturn(page); + given(s3Service.generateFileUrl(documentVersion.getFileKey())).willReturn("https://s3.com/1/naver_resume.pdf"); + + Page actual = documentService.getAllVersions(userId, documentId, Pageable.unpaged()); + + assertThat(actual.getContent()).hasSize(1); + assertThat(actual.getContent().get(0).fileUrl()).isEqualTo("https://s3.com/1/naver_resume.pdf"); + } + + @DisplayName("문서를 삭제하면 S3, 매핑 엔티티, 버전, 문서 순으로 삭제된다") + @Test + void delete() { + given(documentRepository.findById(documentId)).willReturn(Optional.of(document)); + given(documentVersionRepository.findAllByUserIdAndDocumentId(eq(userId), eq(documentId), any(Pageable.class))) + .willReturn(new PageImpl<>(List.of(documentVersion))); + + willDoNothing().given(s3Service).deleteFile(documentVersion.getFileKey()); + willDoNothing().given(applicationFormDocumentService).deleteAllByDocumentId(documentId); + willDoNothing().given(documentVersionRepository).deleteAllByDocumentId(documentId); + willDoNothing().given(documentRepository).delete(document); + + documentService.delete(userId, documentId); + + then(s3Service).should().deleteFile(documentVersion.getFileKey()); + then(applicationFormDocumentService).should().deleteAllByDocumentId(documentId); + then(documentVersionRepository).should().deleteAllByDocumentId(documentId); + then(documentRepository).should().delete(document); + } + + @DisplayName("문서 삭제 시 소유자가 아니면 예외 발생") + @Test + void delete_forbidden() { + User otherUser = UserFixture.createMember(2L, "other@gmail.com", "1234", "other"); + Document otherDoc = Document.builder().user(otherUser).documentType(DocumentType.RESUME).title("타인 문서").build(); + ReflectionTestUtils.setField(otherDoc, "id", 5L); + + given(documentRepository.findById(5L)).willReturn(Optional.of(otherDoc)); + + assertThatThrownBy(() -> documentService.delete(userId, 5L)) + .isInstanceOf(JobNoteException.class) + .hasMessageContaining(FORBIDDEN.getMessage()); + } +} diff --git a/src/test/java/com/jobnote/domain/schedule/service/ScheduleServiceTest.java b/src/test/java/com/jobnote/domain/schedule/service/ScheduleServiceTest.java new file mode 100644 index 0000000..9c049f6 --- /dev/null +++ b/src/test/java/com/jobnote/domain/schedule/service/ScheduleServiceTest.java @@ -0,0 +1,181 @@ +package com.jobnote.domain.schedule.service; + +import com.jobnote.ServiceUnitTest; +import com.jobnote.domain.applicationform.domain.ApplicationForm; +import com.jobnote.domain.applicationform.repository.ApplicationFormRepository; +import com.jobnote.domain.schedule.domain.Schedule; +import com.jobnote.domain.schedule.dto.ScheduleRequest; +import com.jobnote.domain.schedule.dto.ScheduleResponse; +import com.jobnote.domain.schedule.repository.ScheduleRepository; +import com.jobnote.domain.user.domain.User; +import com.jobnote.domain.user.domain.UserFixture; +import com.jobnote.global.exception.JobNoteException; +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.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.test.util.ReflectionTestUtils; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static com.jobnote.domain.applicationform.domain.ApplicationFormStatus.APPLIED; +import static com.jobnote.domain.schedule.domain.ScheduleStatus.*; +import static com.jobnote.global.common.ResponseCode.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.BDDMockito.*; + +class ScheduleServiceTest extends ServiceUnitTest { + + @InjectMocks private ScheduleService scheduleService; + + @Mock private ScheduleRepository scheduleRepository; + @Mock private ApplicationFormRepository applicationFormRepository; + + private ApplicationForm applicationForm; + private Schedule schedule; + + private final Long userId = 1L; + private final Long formId = 1L; + private final Long scheduleId = 1L; + + @BeforeEach + void setUp() { + User user = UserFixture.createMember(userId, "test@gmail.com", "1234", "test"); + applicationForm = ApplicationForm.builder() + .user(user) + .companyName("네이버") + .companyAddress("경기도 성남시 분당구") + .companyUrl("https://naver.com") + .companyScale("대기업") + .position("백엔드 개발자") + .status(APPLIED) + .build(); + ReflectionTestUtils.setField(applicationForm, "id", formId); + + schedule = Schedule.builder() + .applicationForm(applicationForm) + .title("1차 면접") + .memo("화상 면접") + .dateTime(LocalDateTime.of(2025, 9, 10, 10, 0)) + .status(PLANNED) + .build(); + ReflectionTestUtils.setField(schedule, "id", scheduleId); + } + + @Nested + @DisplayName("일정 단건 조회") + class GetById { + + @DisplayName("성공") + @Test + void success() { + // given + given(scheduleRepository.findById(scheduleId)).willReturn(Optional.of(schedule)); + + // when + ScheduleResponse actual = scheduleService.getById(userId, formId, scheduleId); + + // then + assertThat(actual.id()).isEqualTo(scheduleId); + assertThat(actual.title()).isEqualTo("1차 면접"); + } + + @DisplayName("실패 - 일정을 찾을 수 없음") + @Test + void fail_notFound() { + // given + given(scheduleRepository.findById(scheduleId)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> scheduleService.getById(userId, formId, scheduleId)) + .isInstanceOf(JobNoteException.class) + .hasMessageContaining(NOT_FOUND_SCHEDULE.getMessage()); + } + + @DisplayName("실패 - 권한 없음") + @Test + void fail_forbidden() { + // given + given(scheduleRepository.findById(scheduleId)).willReturn(Optional.of(schedule)); + + // when & then + assertThatThrownBy(() -> scheduleService.getById(2L, formId, scheduleId)) + .isInstanceOf(JobNoteException.class) + .hasMessageContaining(FORBIDDEN.getMessage()); + } + } + + @DisplayName("일정 저장 성공") + @Test + void save_success() { + ScheduleRequest request = new ScheduleRequest(null, "코딩 테스트", "온라인", LocalDateTime.now(), PLANNED); + + given(applicationFormRepository.findById(formId)).willReturn(Optional.of(applicationForm)); + given(scheduleRepository.save(any(Schedule.class))).willReturn(schedule); + + Long savedId = scheduleService.save(userId, formId, request); + + assertThat(savedId).isEqualTo(scheduleId); + then(scheduleRepository).should().save(any(Schedule.class)); + } + + @DisplayName("일정 저장 실패 - 지원서 없음") + @Test + void save_notFoundForm() { + ScheduleRequest request = new ScheduleRequest(null, "코딩 테스트", "온라인", LocalDateTime.now(), PLANNED); + given(applicationFormRepository.findById(formId)).willReturn(Optional.empty()); + + assertThatThrownBy(() -> scheduleService.save(userId, formId, request)) + .isInstanceOf(JobNoteException.class) + .hasMessageContaining(NOT_FOUND_APPLICATION_FORM.getMessage()); + } + + @DisplayName("일정 수정 성공") + @Test + void update_success() { + ScheduleRequest updateReq = new ScheduleRequest(scheduleId, "최종 면접", "오프라인", LocalDateTime.now().plusDays(1), PROGRESS); + + given(scheduleRepository.findById(scheduleId)).willReturn(Optional.of(schedule)); + + scheduleService.update(userId, formId, scheduleId, updateReq); + + assertThat(schedule.getTitle()).isEqualTo("최종 면접"); + assertThat(schedule.getStatus()).isEqualTo(PROGRESS); + } + + @DisplayName("일정 삭제 성공") + @Test + void delete_success() { + given(scheduleRepository.findById(scheduleId)).willReturn(Optional.of(schedule)); + willDoNothing().given(scheduleRepository).delete(schedule); + + scheduleService.delete(userId, formId, scheduleId); + + then(scheduleRepository).should().delete(schedule); + } + + @DisplayName("해당 기간의 일정 조회 성공") + @Test + void getAll_success() { + Page page = new PageImpl<>(List.of(schedule)); + + given(scheduleRepository.findAllByUserIdAndDateTimeBetween(eq(userId), any(), any(), any(Pageable.class))) + .willReturn(page); + + Page actual = scheduleService.getAll( + userId, LocalDateTime.now().minusDays(1), LocalDateTime.now().plusDays(1), Pageable.unpaged() + ); + + assertThat(actual.getContent()).hasSize(1); + assertThat(actual.getContent().get(0).title()).isEqualTo("1차 면접"); + } + +}