diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index db05917..5d3b7ec 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -59,3 +59,4 @@ include::{snippets}/course-controller-test/멤버_course_전체_조회/auto-sect include::{snippets}/editor-controller-test/editor_저장/auto-section.adoc[] include::{snippets}/editor-controller-test/editor_역할_수정/auto-section.adoc[] +include::{snippets}/editor-controller-test/editor_역할_삭제/auto-section.adoc[] \ No newline at end of file diff --git a/src/main/java/com/pcb/audy/domain/editor/controller/EditorController.java b/src/main/java/com/pcb/audy/domain/editor/controller/EditorController.java index a3c3af3..bde3bca 100644 --- a/src/main/java/com/pcb/audy/domain/editor/controller/EditorController.java +++ b/src/main/java/com/pcb/audy/domain/editor/controller/EditorController.java @@ -1,7 +1,9 @@ package com.pcb.audy.domain.editor.controller; +import com.pcb.audy.domain.editor.dto.request.EditorDeleteReq; import com.pcb.audy.domain.editor.dto.request.EditorRoleUpdateReq; import com.pcb.audy.domain.editor.dto.request.EditorSaveReq; +import com.pcb.audy.domain.editor.dto.response.EditorDeleteRes; import com.pcb.audy.domain.editor.dto.response.EditorRoleUpdateRes; import com.pcb.audy.domain.editor.dto.response.EditorSaveRes; import com.pcb.audy.domain.editor.service.EditorService; @@ -9,11 +11,7 @@ import com.pcb.audy.global.response.BasicResponse; import lombok.RequiredArgsConstructor; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.PatchMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/v1/editors") @@ -36,4 +34,12 @@ public BasicResponse updateEditorRole( editorRoleUpdateReq.setUserId(principalDetails.getUser().getUserId()); return BasicResponse.success(editorService.updateRoleEditor(editorRoleUpdateReq)); } + + @DeleteMapping + public BasicResponse deleteEditor( + @AuthenticationPrincipal PrincipalDetails principalDetails, + @RequestBody EditorDeleteReq editorDeleteReq) { + editorDeleteReq.setUserId(principalDetails.getUser().getUserId()); + return BasicResponse.success(editorService.deleteEditor(editorDeleteReq)); + } } diff --git a/src/main/java/com/pcb/audy/domain/editor/dto/request/EditorDeleteReq.java b/src/main/java/com/pcb/audy/domain/editor/dto/request/EditorDeleteReq.java new file mode 100644 index 0000000..df77c87 --- /dev/null +++ b/src/main/java/com/pcb/audy/domain/editor/dto/request/EditorDeleteReq.java @@ -0,0 +1,19 @@ +package com.pcb.audy.domain.editor.dto.request; + +import lombok.*; + +@Setter +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class EditorDeleteReq { + private Long userId; + private Long courseId; + private Long selectedUserId; + + @Builder + private EditorDeleteReq(Long userId, Long courseId, Long selectedUserId) { + this.userId = userId; + this.courseId = courseId; + this.selectedUserId = selectedUserId; + } +} diff --git a/src/main/java/com/pcb/audy/domain/editor/dto/response/EditorDeleteRes.java b/src/main/java/com/pcb/audy/domain/editor/dto/response/EditorDeleteRes.java new file mode 100644 index 0000000..ccc668e --- /dev/null +++ b/src/main/java/com/pcb/audy/domain/editor/dto/response/EditorDeleteRes.java @@ -0,0 +1,6 @@ +package com.pcb.audy.domain.editor.dto.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties +public class EditorDeleteRes {} diff --git a/src/main/java/com/pcb/audy/domain/editor/service/EditorService.java b/src/main/java/com/pcb/audy/domain/editor/service/EditorService.java index 75d8706..87cab14 100644 --- a/src/main/java/com/pcb/audy/domain/editor/service/EditorService.java +++ b/src/main/java/com/pcb/audy/domain/editor/service/EditorService.java @@ -6,8 +6,10 @@ import com.pcb.audy.domain.course.dto.request.CourseInviteRedisReq; import com.pcb.audy.domain.course.entity.Course; import com.pcb.audy.domain.course.repository.CourseRepository; +import com.pcb.audy.domain.editor.dto.request.EditorDeleteReq; import com.pcb.audy.domain.editor.dto.request.EditorRoleUpdateReq; import com.pcb.audy.domain.editor.dto.request.EditorSaveReq; +import com.pcb.audy.domain.editor.dto.response.EditorDeleteRes; import com.pcb.audy.domain.editor.dto.response.EditorRoleUpdateRes; import com.pcb.audy.domain.editor.dto.response.EditorSaveRes; import com.pcb.audy.domain.editor.entity.Editor; @@ -61,11 +63,6 @@ public EditorSaveRes saveEditor(EditorSaveReq editorSaveReq) { return EditorServiceMapper.INSTANCE.toEditorSaveRes(savedEditor); } - private void checkAlreadyExistEditor(User user, Course course) { - Editor editor = editorRepository.findByUserAndCourse(user, course); - EditorValidator.checkAlreadyExist(editor); - } - @Transactional public EditorRoleUpdateRes updateRoleEditor(EditorRoleUpdateReq editorRoleUpdateReq) { User user = getUserByUserId(editorRoleUpdateReq.getUserId()); @@ -83,6 +80,19 @@ public EditorRoleUpdateRes updateRoleEditor(EditorRoleUpdateReq editorRoleUpdate return new EditorRoleUpdateRes(); } + public EditorDeleteRes deleteEditor(EditorDeleteReq editorDeleteReq) { + User source = getUserByUserId(editorDeleteReq.getUserId()); + User target = getUserByUserId(editorDeleteReq.getSelectedUserId()); + Course course = getCourseByCourseId(editorDeleteReq.getCourseId()); + + Editor sourceEditor = getEditor(source, course); + Editor targetEditor = getEditor(target, course); + EditorValidator.checkCanDelete(sourceEditor, targetEditor); + + editorRepository.delete(targetEditor); + return new EditorDeleteRes(); + } + private User getUserByUserId(Long userId) { User user = userRepository.findByUserId(userId); UserValidator.validate(user); @@ -100,4 +110,9 @@ private Editor getEditor(User user, Course course) { EditorValidator.validate(editor); return editor; } + + private void checkAlreadyExistEditor(User user, Course course) { + Editor editor = editorRepository.findByUserAndCourse(user, course); + EditorValidator.checkAlreadyExist(editor); + } } diff --git a/src/main/java/com/pcb/audy/global/response/ResultCode.java b/src/main/java/com/pcb/audy/global/response/ResultCode.java index 7ee7ec1..d67b2d8 100644 --- a/src/main/java/com/pcb/audy/global/response/ResultCode.java +++ b/src/main/java/com/pcb/audy/global/response/ResultCode.java @@ -24,6 +24,7 @@ public enum ResultCode { NOT_FOUND_EDITOR(5000, "에디터를 찾을 수 없습니다."), NOT_VALID_KEY(5001, "유효기간이 만료된 링크입니다."), ALREADY_EXIST_EDITOR(5002, "이미 코스에 포함된 멤버입니다."), + FAILED_DELETE_EDITOR(5003, "해당 유저를 코스에서 내보낼 수 없습니다."), FAILED_ENCRYPT(6000, "객체 암호화에 실패하였습니다."), FAILED_DECRYPT(6001, "객체 복호화에 실패하였습니다."); diff --git a/src/main/java/com/pcb/audy/global/validator/EditorValidator.java b/src/main/java/com/pcb/audy/global/validator/EditorValidator.java index 05a9429..0bf0d00 100644 --- a/src/main/java/com/pcb/audy/global/validator/EditorValidator.java +++ b/src/main/java/com/pcb/audy/global/validator/EditorValidator.java @@ -27,6 +27,12 @@ public static void checkIsAdminUser(Editor editor) { } } + public static void checkCanDelete(Editor source, Editor target) { + if (isAdminEditor(target) || (!isEqualUser(source, target) && !isAdminEditor(source))) { + throw new GlobalException(FAILED_DELETE_EDITOR); + } + } + public static void checkAlreadyExist(Editor editor) { if (isExistEditor(editor)) { throw new GlobalException(ALREADY_EXIST_EDITOR); @@ -52,8 +58,16 @@ private static boolean isAdminEditor(Editor editor) { return Role.OWNER.equals(editor.getRole()); } + private static boolean isMemberEditor(Editor editor) { + return Role.MEMBER.equals(editor.getRole()); + } + private static boolean isEqualObject( CourseInviteRedisReq courseInviteRedisReq, CourseInviteRedisReq findByKey) { return courseInviteRedisReq.equals(findByKey); } + + private static boolean isEqualUser(Editor source, Editor target) { + return source.getUser().getUserId() == target.getUser().getUserId(); + } } diff --git a/src/test/java/com/pcb/audy/domain/editor/controller/EditorControllerTest.java b/src/test/java/com/pcb/audy/domain/editor/controller/EditorControllerTest.java index e4067c4..801f7f0 100644 --- a/src/test/java/com/pcb/audy/domain/editor/controller/EditorControllerTest.java +++ b/src/test/java/com/pcb/audy/domain/editor/controller/EditorControllerTest.java @@ -3,14 +3,17 @@ import static com.pcb.audy.global.meta.Role.MEMBER; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.pcb.audy.domain.BaseMvcTest; +import com.pcb.audy.domain.editor.dto.request.EditorDeleteReq; import com.pcb.audy.domain.editor.dto.request.EditorRoleUpdateReq; import com.pcb.audy.domain.editor.dto.request.EditorSaveReq; +import com.pcb.audy.domain.editor.dto.response.EditorDeleteRes; import com.pcb.audy.domain.editor.dto.response.EditorRoleUpdateRes; import com.pcb.audy.domain.editor.dto.response.EditorSaveRes; import com.pcb.audy.domain.editor.service.EditorService; @@ -61,4 +64,25 @@ class EditorControllerTest extends BaseMvcTest implements EditorTest { .andDo(print()) .andExpect(status().isOk()); } + + @Test + @DisplayName("editor 역할 삭제 테스트") + void editor_역할_삭제() throws Exception { + EditorDeleteReq editorDeleteReq = + EditorDeleteReq.builder() + .courseId(TEST_COURSE_ID) + .selectedUserId(TEST_ANOTHER_USER_ID) + .build(); + EditorDeleteRes editorDeleteRes = new EditorDeleteRes(); + + when(editorService.deleteEditor(any())).thenReturn(editorDeleteRes); + this.mockMvc + .perform( + delete("/v1/editors") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(editorDeleteReq)) + .principal(this.mockPrincipal)) + .andDo(print()) + .andExpect(status().isOk()); + } } diff --git a/src/test/java/com/pcb/audy/domain/editor/service/EditorServiceTest.java b/src/test/java/com/pcb/audy/domain/editor/service/EditorServiceTest.java index 712335e..b548510 100644 --- a/src/test/java/com/pcb/audy/domain/editor/service/EditorServiceTest.java +++ b/src/test/java/com/pcb/audy/domain/editor/service/EditorServiceTest.java @@ -13,6 +13,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.pcb.audy.domain.course.dto.request.CourseInviteRedisReq; import com.pcb.audy.domain.course.repository.CourseRepository; +import com.pcb.audy.domain.editor.dto.request.EditorDeleteReq; import com.pcb.audy.domain.editor.dto.request.EditorRoleUpdateReq; import com.pcb.audy.domain.editor.dto.request.EditorSaveReq; import com.pcb.audy.domain.editor.repository.EditorRepository; @@ -178,4 +179,95 @@ class editor_역할_수정 { assertThat(exception.getResultCode()).isEqualTo(NOT_ADMIN_COURSE); } } + + @Nested + class editor_역할_삭제 { + @Test + @DisplayName("editor 역할 삭제 테스트") + void editor_역할_삭제() { + // given + EditorDeleteReq editorDeleteReq = + EditorDeleteReq.builder() + .userId(TEST_USER_ID) + .selectedUserId(TEST_ANOTHER_USER_ID) + .build(); + + when(userRepository.findByUserId(any())).thenReturn(TEST_USER).thenReturn(TEST_ANOTHER_USER); + when(courseRepository.findByCourseId(any())).thenReturn(TEST_COURSE); + when(editorRepository.findByUserAndCourse(any(), any())) + .thenReturn(TEST_EDITOR_ADMIN) + .thenReturn(TEST_EDITOR_MEMBER); + + // when + editorService.deleteEditor(editorDeleteReq); + + // then + verify(userRepository, times(2)).findByUserId(any()); + verify(courseRepository).findByCourseId(any()); + verify(editorRepository, times(2)).findByUserAndCourse(any(), any()); + verify(editorRepository).delete(any()); + } + + @Test + @DisplayName("editor 역할 수정 실패 테스트 - 관리자가 아닌 사람이 타인을 삭제하려고 할 때") + void editor_MEMBER가_역할_수정_실패() { + // given + EditorDeleteReq editorDeleteReq = + EditorDeleteReq.builder() + .userId(TEST_USER_ID) + .selectedUserId(TEST_ANOTHER_USER_ID) + .build(); + + when(userRepository.findByUserId(any())).thenReturn(TEST_USER).thenReturn(TEST_ANOTHER_USER); + when(courseRepository.findByCourseId(any())).thenReturn(TEST_COURSE); + when(editorRepository.findByUserAndCourse(any(), any())) + .thenReturn(TEST_EDITOR_MEMBER) + .thenReturn(TEST_ANOTHER_EDITOR_MEMBER); + + // when + GlobalException exception = + assertThrows( + GlobalException.class, + () -> { + editorService.deleteEditor(editorDeleteReq); + }); + + // then + verify(userRepository, times(2)).findByUserId(any()); + verify(courseRepository).findByCourseId(any()); + verify(editorRepository, times(2)).findByUserAndCourse(any(), any()); + assertThat(exception.getResultCode()).isEqualTo(FAILED_DELETE_EDITOR); + } + + @Test + @DisplayName("editor 역할 수정 실패 테스트 - 삭제 대상이 admin인 경우") + void editor_처리대상이_ADMIN_역할_수정_실패() { + // given + EditorDeleteReq editorDeleteReq = + EditorDeleteReq.builder() + .userId(TEST_USER_ID) + .selectedUserId(TEST_ANOTHER_USER_ID) + .build(); + + when(userRepository.findByUserId(any())).thenReturn(TEST_USER).thenReturn(TEST_ANOTHER_USER); + when(courseRepository.findByCourseId(any())).thenReturn(TEST_COURSE); + when(editorRepository.findByUserAndCourse(any(), any())) + .thenReturn(TEST_EDITOR_ADMIN) + .thenReturn(TEST_EDITOR_ADMIN); + + // when + GlobalException exception = + assertThrows( + GlobalException.class, + () -> { + editorService.deleteEditor(editorDeleteReq); + }); + + // then + verify(userRepository, times(2)).findByUserId(any()); + verify(courseRepository).findByCourseId(any()); + verify(editorRepository, times(2)).findByUserAndCourse(any(), any()); + assertThat(exception.getResultCode()).isEqualTo(FAILED_DELETE_EDITOR); + } + } } diff --git a/src/test/java/com/pcb/audy/test/EditorTest.java b/src/test/java/com/pcb/audy/test/EditorTest.java index 2d3d564..b2b8b24 100644 --- a/src/test/java/com/pcb/audy/test/EditorTest.java +++ b/src/test/java/com/pcb/audy/test/EditorTest.java @@ -12,4 +12,7 @@ public interface EditorTest extends CourseTest, UserTest { Editor TEST_EDITOR_MEMBER = Editor.builder().course(TEST_SECOND_COURSE).user(TEST_USER).role(Role.MEMBER).build(); + + Editor TEST_ANOTHER_EDITOR_MEMBER = + Editor.builder().course(TEST_SECOND_COURSE).user(TEST_ANOTHER_USER).role(Role.MEMBER).build(); }