From 0739181ba5b2e886f3c7e1fab96b3a673cf4429b Mon Sep 17 00:00:00 2001 From: Arachne <66822642+Arachneee@users.noreply.github.com> Date: Thu, 1 Aug 2024 10:14:47 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=98=88=EC=99=B8=EB=A9=94=EC=8B=9C?= =?UTF-8?q?=EC=A7=80=20=EA=B5=AC=EC=B2=B4=ED=99=94=20(#161)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 예외 핸들링 추가 * refactor: 예외 메시지 구체화 및 검증 역할 변경 * feat: 에러 코드 추가 * style: 개행 제거 * refactor: 멤버 액션 예외 ErrorCode 분리 * feat: 로깅 추가 --- .../application/MemberActionFactory.java | 35 +++--------- .../application/MemberActionService.java | 3 +- .../domain/action/CurrentMembers.java | 13 ++++- .../haengdong/exception/ErrorResponse.java | 9 ++- .../exception/GlobalExceptionHandler.java | 24 +++++--- .../exception/HaengdongErrorCode.java | 22 ++++---- .../exception/HaengdongException.java | 5 -- .../application/MemberActionFactoryTest.java | 55 +++++++++++++------ .../domain/action/CurrentMembersTest.java | 40 ++++++++++++++ 9 files changed, 134 insertions(+), 72 deletions(-) diff --git a/server/src/main/java/server/haengdong/application/MemberActionFactory.java b/server/src/main/java/server/haengdong/application/MemberActionFactory.java index cbe34f0ef..9d9afb6ac 100644 --- a/server/src/main/java/server/haengdong/application/MemberActionFactory.java +++ b/server/src/main/java/server/haengdong/application/MemberActionFactory.java @@ -8,6 +8,7 @@ import server.haengdong.application.request.MemberActionSaveAppRequest; import server.haengdong.application.request.MemberActionsSaveAppRequest; import server.haengdong.domain.action.Action; +import server.haengdong.domain.action.CurrentMembers; import server.haengdong.domain.action.MemberAction; import server.haengdong.domain.action.MemberActionStatus; import server.haengdong.domain.action.MemberGroupIdProvider; @@ -22,11 +23,11 @@ public class MemberActionFactory { public List createMemberActions( MemberActionsSaveAppRequest request, - List memberActions, + CurrentMembers currentMembers, Action action ) { validateMemberNames(request); - validateActions(request, memberActions); + validateActions(request, currentMembers); Long memberGroupId = memberGroupIdProvider.createGroupId(); List createdMemberActions = new ArrayList<>(); @@ -51,32 +52,12 @@ private void validateMemberNames(MemberActionsSaveAppRequest request) { } } - private void validateActions(MemberActionsSaveAppRequest request, List memberActions) { - List reverseSortedMemberActions = memberActions.stream() - .sorted(Comparator.comparing(MemberAction::getSequence).reversed()) - .toList(); - - for (MemberActionSaveAppRequest action : request.actions()) { - validateAction(action, reverseSortedMemberActions); - } - } + private void validateActions(MemberActionsSaveAppRequest request, CurrentMembers currentMembers) { + List actions = request.actions(); - private void validateAction(MemberActionSaveAppRequest request, List memberActions) { - MemberActionStatus memberActionStatus = MemberActionStatus.of(request.status()); - if (isInvalidStatus(memberActions, request.name(), memberActionStatus)) { - throw new HaengdongException(HaengdongErrorCode.INVALID_MEMBER_ACTION); + for (MemberActionSaveAppRequest action : actions) { + MemberActionStatus memberActionStatus = MemberActionStatus.of(action.status()); + currentMembers.validate(action.name(), memberActionStatus); } } - - private boolean isInvalidStatus( - List memberActions, - String memberName, - MemberActionStatus memberActionStatus - ) { - return memberActions.stream() - .filter(action -> action.isSameName(memberName)) - .findFirst() - .map(action -> action.isSameStatus(memberActionStatus)) - .orElse(MemberActionStatus.IN != memberActionStatus); - } } diff --git a/server/src/main/java/server/haengdong/application/MemberActionService.java b/server/src/main/java/server/haengdong/application/MemberActionService.java index 5094bf2c0..f893bd08d 100644 --- a/server/src/main/java/server/haengdong/application/MemberActionService.java +++ b/server/src/main/java/server/haengdong/application/MemberActionService.java @@ -31,8 +31,9 @@ public void saveMemberAction(String token, MemberActionsSaveAppRequest request) Event event = findEvent(token); List findMemberActions = memberActionRepository.findAllByEvent(event); + CurrentMembers currentMembers = CurrentMembers.of(findMemberActions); Action action = createStartAction(event); - List memberActions = memberActionFactory.createMemberActions(request, findMemberActions, action); + List memberActions = memberActionFactory.createMemberActions(request, currentMembers, action); memberActionRepository.saveAll(memberActions); } diff --git a/server/src/main/java/server/haengdong/domain/action/CurrentMembers.java b/server/src/main/java/server/haengdong/domain/action/CurrentMembers.java index 298a8a965..082b9139d 100644 --- a/server/src/main/java/server/haengdong/domain/action/CurrentMembers.java +++ b/server/src/main/java/server/haengdong/domain/action/CurrentMembers.java @@ -5,6 +5,8 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import server.haengdong.exception.HaengdongErrorCode; +import server.haengdong.exception.HaengdongException; public class CurrentMembers { @@ -14,7 +16,7 @@ public CurrentMembers() { this(new HashSet<>()); } - private CurrentMembers(Set members) { + protected CurrentMembers(Set members) { this.members = members; } @@ -52,6 +54,15 @@ public CurrentMembers addMemberAction(MemberAction memberAction) { return new CurrentMembers(currentMembers); } + public void validate(String memberName, MemberActionStatus memberActionStatus) { + if (memberActionStatus == MemberActionStatus.IN && members.contains(memberName)) { + throw new HaengdongException(HaengdongErrorCode.INVALID_MEMBER_IN_ACTION); + } + if (memberActionStatus == MemberActionStatus.OUT && !members.contains(memberName)) { + throw new HaengdongException(HaengdongErrorCode.INVALID_MEMBER_OUT_ACTION); + } + } + public boolean isEmpty() { return members.isEmpty(); } diff --git a/server/src/main/java/server/haengdong/exception/ErrorResponse.java b/server/src/main/java/server/haengdong/exception/ErrorResponse.java index c797b811a..69133a66c 100644 --- a/server/src/main/java/server/haengdong/exception/ErrorResponse.java +++ b/server/src/main/java/server/haengdong/exception/ErrorResponse.java @@ -1,10 +1,15 @@ package server.haengdong.exception; public record ErrorResponse( + String code, String message ) { - public static ErrorResponse of(String message) { - return new ErrorResponse(message); + public static ErrorResponse of(HaengdongErrorCode errorCode) { + return new ErrorResponse(errorCode.getCode(), errorCode.getMessage()); + } + + public static ErrorResponse of(HaengdongErrorCode errorCode, String message){ + return new ErrorResponse(errorCode.getCode(), message); } } diff --git a/server/src/main/java/server/haengdong/exception/GlobalExceptionHandler.java b/server/src/main/java/server/haengdong/exception/GlobalExceptionHandler.java index bb3138cc7..fb15afc56 100644 --- a/server/src/main/java/server/haengdong/exception/GlobalExceptionHandler.java +++ b/server/src/main/java/server/haengdong/exception/GlobalExceptionHandler.java @@ -3,41 +3,51 @@ import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.resource.NoResourceFoundException; @Slf4j @RestControllerAdvice public class GlobalExceptionHandler { - @ExceptionHandler(HttpRequestMethodNotSupportedException.class) - public ResponseEntity haengdongException() { + @ExceptionHandler({HttpRequestMethodNotSupportedException.class, NoResourceFoundException.class}) + public ResponseEntity noResourceException() { return ResponseEntity.badRequest() - .body(ErrorResponse.of(HaengdongErrorCode.BAD_REQUEST.getMessage())); + .body(ErrorResponse.of(HaengdongErrorCode.NO_RESOURCE_REQUEST)); + } + + @ExceptionHandler(HttpMessageNotReadableException.class) + public ResponseEntity httpMessageNotReadableException() { + return ResponseEntity.badRequest() + .body(ErrorResponse.of(HaengdongErrorCode.MESSAGE_NOT_READABLE)); } @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + log.info(e.getMessage(), e); String errorMessage = e.getFieldErrors().stream() .map(error -> error.getField() + " " + error.getDefaultMessage()) .collect(Collectors.joining(", ")); return ResponseEntity.badRequest() - .body(ErrorResponse.of(errorMessage)); + .body(ErrorResponse.of(HaengdongErrorCode.BAD_REQUEST, errorMessage)); } @ExceptionHandler(HaengdongException.class) public ResponseEntity haengdongException(HaengdongException e) { - return ResponseEntity.status(e.getStatusCode()) - .body(ErrorResponse.of(e.getMessage())); + log.info(e.getMessage(), e); + return ResponseEntity.badRequest() + .body(ErrorResponse.of(e.getErrorCode())); } @ExceptionHandler(Exception.class) public ResponseEntity handleException(Exception e) { log.error(e.getMessage(), e); return ResponseEntity.internalServerError() - .body(ErrorResponse.of(HaengdongErrorCode.INTERNAL_SERVER_ERROR.getMessage())); + .body(ErrorResponse.of(HaengdongErrorCode.INTERNAL_SERVER_ERROR)); } } diff --git a/server/src/main/java/server/haengdong/exception/HaengdongErrorCode.java b/server/src/main/java/server/haengdong/exception/HaengdongErrorCode.java index 792baa838..052b97fe2 100644 --- a/server/src/main/java/server/haengdong/exception/HaengdongErrorCode.java +++ b/server/src/main/java/server/haengdong/exception/HaengdongErrorCode.java @@ -1,24 +1,24 @@ package server.haengdong.exception; import lombok.Getter; -import org.springframework.http.HttpStatus; @Getter public enum HaengdongErrorCode { - BAD_REQUEST(HttpStatus.BAD_REQUEST, "잘못된 요청입니다."), - DUPLICATED_MEMBER_ACTION(HttpStatus.BAD_REQUEST, "올바르지 않은 인원 요청입니다."), - INVALID_MEMBER_ACTION(HttpStatus.BAD_REQUEST, "잘못된 맴버 액션입니다."), - - NOT_FOUND_EVENT(HttpStatus.NOT_FOUND, "존재하지 않는 행사입니다."), - - INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부에서 에러가 발생했습니다."), + BAD_REQUEST("R_001", "잘못된 요청입니다."), + NO_RESOURCE_REQUEST("R_002", "잘못된 엔드포인트입니다."), + MESSAGE_NOT_READABLE("R_003", "읽을 수 없는 요청 형식입니다."), + DUPLICATED_MEMBER_ACTION("MA_001", "중복된 인원이 존재합니다."), + INVALID_MEMBER_IN_ACTION("MA_002", "현재 참여하고 있는 인원이 존재합니다."), + INVALID_MEMBER_OUT_ACTION("MA_003", "현재 참여하고 있지 않는 인원이 존재합니다."), + NOT_FOUND_EVENT("EV_400", "존재하지 않는 행사입니다."), + INTERNAL_SERVER_ERROR("S_001", "서버 내부에서 에러가 발생했습니다."), ; - private final HttpStatus httpStatus; + private final String code; private final String message; - HaengdongErrorCode(HttpStatus httpStatus, String message) { - this.httpStatus = httpStatus; + HaengdongErrorCode(String code, String message) { + this.code = code; this.message = message; } } diff --git a/server/src/main/java/server/haengdong/exception/HaengdongException.java b/server/src/main/java/server/haengdong/exception/HaengdongException.java index 812df50f8..d418a2feb 100644 --- a/server/src/main/java/server/haengdong/exception/HaengdongException.java +++ b/server/src/main/java/server/haengdong/exception/HaengdongException.java @@ -1,7 +1,6 @@ package server.haengdong.exception; import lombok.Getter; -import org.springframework.http.HttpStatusCode; @Getter public class HaengdongException extends RuntimeException { @@ -18,10 +17,6 @@ public HaengdongException(HaengdongErrorCode errorCode, String message) { this.message = message; } - public HttpStatusCode getStatusCode() { - return errorCode.getHttpStatus(); - } - @Override public String getMessage() { if (message == null) { diff --git a/server/src/test/java/server/haengdong/application/MemberActionFactoryTest.java b/server/src/test/java/server/haengdong/application/MemberActionFactoryTest.java index 40bee2ff6..278423759 100644 --- a/server/src/test/java/server/haengdong/application/MemberActionFactoryTest.java +++ b/server/src/test/java/server/haengdong/application/MemberActionFactoryTest.java @@ -14,6 +14,7 @@ import server.haengdong.application.request.MemberActionSaveAppRequest; import server.haengdong.application.request.MemberActionsSaveAppRequest; import server.haengdong.domain.action.Action; +import server.haengdong.domain.action.CurrentMembers; import server.haengdong.domain.event.Event; import server.haengdong.domain.action.MemberAction; import server.haengdong.domain.action.MemberActionStatus; @@ -56,10 +57,12 @@ void createMemberActionsTest() { MemberActionsSaveAppRequest request = new MemberActionsSaveAppRequest( List.of(new MemberActionSaveAppRequest("토다리", "OUT"))); + List unorderedMemberActions = List.of(memberAction2, memberAction1); + CurrentMembers currentMembers = CurrentMembers.of(unorderedMemberActions); Action startAction = new Action(event, 3L); - assertThatThrownBy(() -> memberActionFactory.createMemberActions(request, unorderedMemberActions, startAction)) + assertThatThrownBy(() -> memberActionFactory.createMemberActions(request, currentMembers, startAction)) .isInstanceOf(HaengdongException.class); } @@ -75,8 +78,10 @@ void createMemberActionsTest1() { List.of(new MemberActionSaveAppRequest("토다리", "OUT"))); Action startAction = new Action(event, 2L); + CurrentMembers currentMembers = CurrentMembers.of(List.of(memberAction)); List memberActions = memberActionFactory.createMemberActions(memberActionsSaveAppRequest, - List.of(memberAction), startAction); + currentMembers, startAction + ); assertThat(memberActions).hasSize(1) .extracting(MemberAction::getAction, MemberAction::getMemberName, MemberAction::getStatus) @@ -96,8 +101,9 @@ void createMemberActionsTest2() { MemberActionsSaveAppRequest request = new MemberActionsSaveAppRequest( List.of(new MemberActionSaveAppRequest("토다리", "OUT"))); Action startAction = new Action(event, 2L); + CurrentMembers currentMembers = CurrentMembers.of(List.of(memberAction)); - assertThatCode(() -> memberActionFactory.createMemberActions(request, List.of(memberAction), startAction)) + assertThatCode(() -> memberActionFactory.createMemberActions(request, currentMembers, startAction)) .doesNotThrowAnyException(); } @@ -115,10 +121,9 @@ void createMemberActionsTest3() { MemberActionsSaveAppRequest request = new MemberActionsSaveAppRequest( List.of(new MemberActionSaveAppRequest("토다리", "IN"))); Action startAction = new Action(event, 3L); + CurrentMembers currentMembers = CurrentMembers.of(List.of(memberAction1, memberAction2)); - assertThatCode( - () -> memberActionFactory.createMemberActions(request, List.of(memberAction1, memberAction2), - startAction)) + assertThatCode(() -> memberActionFactory.createMemberActions(request, currentMembers, startAction)) .doesNotThrowAnyException(); } @@ -133,8 +138,9 @@ void createMemberActionsTest4() { MemberActionsSaveAppRequest request = new MemberActionsSaveAppRequest( List.of(new MemberActionSaveAppRequest("쿠키", "IN"))); Action startAction = new Action(event, 2L); + CurrentMembers currentMembers = CurrentMembers.of(List.of(memberAction)); - assertThatCode(() -> memberActionFactory.createMemberActions(request, List.of(memberAction), startAction)) + assertThatCode(() -> memberActionFactory.createMemberActions(request, currentMembers, startAction)) .doesNotThrowAnyException(); } @@ -146,8 +152,10 @@ void createMemberActionTest5() { MemberActionsSaveAppRequest request = new MemberActionsSaveAppRequest( List.of(new MemberActionSaveAppRequest("쿠키", "OUT"))); Action startAction = new Action(event, 2L); + CurrentMembers currentMembers = CurrentMembers.of(List.of()); - assertThatCode(() -> memberActionFactory.createMemberActions(request, List.of(), startAction)) + assertThatCode( + () -> memberActionFactory.createMemberActions(request, currentMembers, startAction)) .isInstanceOf(HaengdongException.class); } @@ -162,8 +170,9 @@ void createMemberActionTest6() { MemberActionsSaveAppRequest request = new MemberActionsSaveAppRequest( List.of(new MemberActionSaveAppRequest("쿠키", "IN"))); Action startAction = new Action(event, 2L); + CurrentMembers currentMembers = CurrentMembers.of(List.of(memberAction)); - assertThatCode(() -> memberActionFactory.createMemberActions(request, List.of(memberAction), startAction)) + assertThatCode(() -> memberActionFactory.createMemberActions(request, currentMembers, startAction)) .isInstanceOf(HaengdongException.class); } @@ -173,11 +182,15 @@ void createMemberActionTest7() { Event event = eventRepository.save(new Event("test", "TOKEN")); MemberActionsSaveAppRequest request = new MemberActionsSaveAppRequest( - List.of(new MemberActionSaveAppRequest("쿠키", "IN"), - new MemberActionSaveAppRequest("쿠키", "IN"))); + List.of( + new MemberActionSaveAppRequest("쿠키", "IN"), + new MemberActionSaveAppRequest("쿠키", "IN") + )); Action startAction = new Action(event, 1L); + CurrentMembers currentMembers = CurrentMembers.of(List.of()); - assertThatCode(() -> memberActionFactory.createMemberActions(request, List.of(), startAction)) + assertThatCode( + () -> memberActionFactory.createMemberActions(request, currentMembers, startAction)) .isInstanceOf(HaengdongException.class); } @@ -190,11 +203,14 @@ void createMemberActionTest8() { memberActionRepository.save(memberAction); MemberActionsSaveAppRequest request = new MemberActionsSaveAppRequest( - List.of(new MemberActionSaveAppRequest("쿠키", "OUT"), - new MemberActionSaveAppRequest("쿠키", "OUT"))); + List.of( + new MemberActionSaveAppRequest("쿠키", "OUT"), + new MemberActionSaveAppRequest("쿠키", "OUT") + )); Action startAction = new Action(event, 2L); + CurrentMembers currentMembers = CurrentMembers.of(List.of(memberAction)); - assertThatCode(() -> memberActionFactory.createMemberActions(request, List.of(memberAction), startAction)) + assertThatCode(() -> memberActionFactory.createMemberActions(request, currentMembers, startAction)) .isInstanceOf(HaengdongException.class); } @@ -207,11 +223,14 @@ void createMemberActionTest9() { memberActionRepository.save(memberAction); MemberActionsSaveAppRequest request = new MemberActionsSaveAppRequest( - List.of(new MemberActionSaveAppRequest("쿠키", "IN"), - new MemberActionSaveAppRequest("쿠키", "OUT"))); + List.of( + new MemberActionSaveAppRequest("쿠키", "IN"), + new MemberActionSaveAppRequest("쿠키", "OUT") + )); Action startAction = new Action(event, 2L); + CurrentMembers currentMembers = CurrentMembers.of(List.of(memberAction)); - assertThatCode(() -> memberActionFactory.createMemberActions(request, List.of(memberAction), startAction)) + assertThatCode(() -> memberActionFactory.createMemberActions(request, currentMembers, startAction)) .isInstanceOf(HaengdongException.class); } } diff --git a/server/src/test/java/server/haengdong/domain/action/CurrentMembersTest.java b/server/src/test/java/server/haengdong/domain/action/CurrentMembersTest.java index 389ca70c1..3ed02ddc5 100644 --- a/server/src/test/java/server/haengdong/domain/action/CurrentMembersTest.java +++ b/server/src/test/java/server/haengdong/domain/action/CurrentMembersTest.java @@ -1,6 +1,7 @@ package server.haengdong.domain.action; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; import static server.haengdong.domain.action.MemberActionStatus.IN; import static server.haengdong.domain.action.MemberActionStatus.OUT; @@ -8,7 +9,10 @@ import java.util.Set; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import server.haengdong.application.request.MemberActionSaveAppRequest; +import server.haengdong.application.request.MemberActionsSaveAppRequest; import server.haengdong.domain.event.Event; +import server.haengdong.exception.HaengdongException; class CurrentMembersTest { @@ -55,4 +59,40 @@ void addMemberAction2() { assertThat(addedCurrentMembers.getMembers()).hasSize(0); } + + @DisplayName("현재 참여중인 인원은 나갈 수 있다.") + @Test + void validate1() { + CurrentMembers currentMembers = new CurrentMembers(Set.of("토다리")); + + assertThatCode(() -> currentMembers.validate("토다리", OUT)) + .doesNotThrowAnyException(); + } + + @DisplayName("현재 참여중이지 않은 인원은 들어올 수 있다.") + @Test + void validate2() { + CurrentMembers currentMembers = new CurrentMembers(Set.of("쿠키")); + + assertThatCode(() -> currentMembers.validate("토다리", IN)) + .doesNotThrowAnyException(); + } + + @DisplayName("현재 참여중인 인원은 들어올 수 없다.") + @Test + void validate3() { + CurrentMembers currentMembers = new CurrentMembers(Set.of("토다리")); + + assertThatCode(() -> currentMembers.validate("토다리", IN)) + .isInstanceOf(HaengdongException.class); + } + + @DisplayName("현재 참여중이지 않은 인원은 나갈 수 없다.") + @Test + void validate4() { + CurrentMembers currentMembers = new CurrentMembers(Set.of("쿠키")); + + assertThatCode(() -> currentMembers.validate("토다리", OUT)) + .isInstanceOf(HaengdongException.class); + } }