diff --git a/src/main/java/org/ioteatime/meonghanyangserver/clients/kvs/KvsClient.java b/src/main/java/org/ioteatime/meonghanyangserver/clients/kvs/KvsClient.java index 275d5929..fd1031ad 100644 --- a/src/main/java/org/ioteatime/meonghanyangserver/clients/kvs/KvsClient.java +++ b/src/main/java/org/ioteatime/meonghanyangserver/clients/kvs/KvsClient.java @@ -1,10 +1,7 @@ package org.ioteatime.meonghanyangserver.clients.kvs; import com.amazonaws.services.kinesisvideo.AmazonKinesisVideo; -import com.amazonaws.services.kinesisvideo.model.DeleteSignalingChannelRequest; -import com.amazonaws.services.kinesisvideo.model.DescribeSignalingChannelRequest; -import com.amazonaws.services.kinesisvideo.model.DescribeSignalingChannelResult; -import com.amazonaws.services.kinesisvideo.model.ResourceNotFoundException; +import com.amazonaws.services.kinesisvideo.model.*; import lombok.RequiredArgsConstructor; import org.ioteatime.meonghanyangserver.common.exception.NotFoundException; import org.ioteatime.meonghanyangserver.common.type.AwsErrorType; @@ -34,4 +31,11 @@ public void deleteSignalingChannel(String channelName) { throw new NotFoundException(AwsErrorType.KVS_CHANNEL_NAME_NOT_FOUND); } } + + public void createSignalingChannel(String channelName) { + CreateSignalingChannelRequest signalingChannelRequest = new CreateSignalingChannelRequest(); + signalingChannelRequest.setChannelName(channelName); + + amazonKinesisVideo.createSignalingChannel(signalingChannelRequest); + } } diff --git a/src/main/java/org/ioteatime/meonghanyangserver/common/type/CommonErrorType.java b/src/main/java/org/ioteatime/meonghanyangserver/common/type/CommonErrorType.java index dd8566ca..908828c8 100644 --- a/src/main/java/org/ioteatime/meonghanyangserver/common/type/CommonErrorType.java +++ b/src/main/java/org/ioteatime/meonghanyangserver/common/type/CommonErrorType.java @@ -15,7 +15,11 @@ public enum CommonErrorType implements ErrorTypeCode { // 인증 실패 UNAUTHORIZED("UNAUTHORIZED", "인증 실패"), INVALID_TYPE("INVALID TYPE", "타입 검증 실패"), - INVALID_BODY("INVALID BODY", "요청 형식이 올바르지 않습니다."); + INVALID_BODY("INVALID BODY", "요청 형식이 올바르지 않습니다."), + NO_SUCH_ELEMENT("NO SUCH ELEMENT", "요소가 존재하지 않습니다"), + IO("I/O", "I/O 오류가 발생하였습니다."), + ILLEGAL_ARGUMENT("ILLEGAL ARGUMENT", "인자 값이 올바르지 않습니다."), + INTERNAL_SERVER("INTERNAL SERVER", "서버 에러가 발생하였습니다. 로그를 확인해 주세요."); private final String message; private final String description; diff --git a/src/main/java/org/ioteatime/meonghanyangserver/handler/GlobalExceptionHandler.java b/src/main/java/org/ioteatime/meonghanyangserver/handler/GlobalExceptionHandler.java index 3f427ec1..5059e95b 100644 --- a/src/main/java/org/ioteatime/meonghanyangserver/handler/GlobalExceptionHandler.java +++ b/src/main/java/org/ioteatime/meonghanyangserver/handler/GlobalExceptionHandler.java @@ -1,10 +1,12 @@ package org.ioteatime.meonghanyangserver.handler; import jakarta.validation.UnexpectedTypeException; +import java.io.IOException; +import java.util.NoSuchElementException; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.ioteatime.meonghanyangserver.common.api.Api; -import org.ioteatime.meonghanyangserver.common.exception.ApiException; -import org.ioteatime.meonghanyangserver.common.exception.ApiExceptionImpl; +import org.ioteatime.meonghanyangserver.common.slack.SlackService; import org.ioteatime.meonghanyangserver.common.type.CommonErrorType; import org.springframework.core.annotation.Order; import org.springframework.dao.DataIntegrityViolationException; @@ -18,22 +20,60 @@ @RestController @Order(value = Integer.MAX_VALUE) @RestControllerAdvice +@RequiredArgsConstructor public class GlobalExceptionHandler { + private final SlackService slackService; + @ExceptionHandler(DataIntegrityViolationException.class) public ResponseEntity> exception(final DataIntegrityViolationException exception) { log.error("{}", exception); + slackService.sendSlackMessage(HttpStatus.BAD_REQUEST, exception, "error"); return new ResponseEntity<>(Api.fail(CommonErrorType.INVALID_BODY), HttpStatus.BAD_REQUEST); } @ExceptionHandler(UnexpectedTypeException.class) public ResponseEntity> exception(final UnexpectedTypeException exception) { log.error("{}", exception); + slackService.sendSlackMessage(HttpStatus.BAD_REQUEST, exception, "error"); return new ResponseEntity<>(Api.fail(CommonErrorType.INVALID_TYPE), HttpStatus.BAD_REQUEST); } - @ExceptionHandler(ApiExceptionImpl.class) - public ResponseEntity> exception(ApiException exception) { - log.info("", exception); - return ResponseEntity.status(exception.getHttpStatus()).body(Api.fail(exception)); + @ExceptionHandler(NullPointerException.class) + public ResponseEntity> exception(final NullPointerException exception) { + log.error("{}", exception); + slackService.sendSlackMessage(HttpStatus.INTERNAL_SERVER_ERROR, exception, "error"); + return new ResponseEntity<>( + Api.fail(CommonErrorType.NULL_POINT), HttpStatus.INTERNAL_SERVER_ERROR); + } + + @ExceptionHandler(NoSuchElementException.class) + public ResponseEntity> exception(final NoSuchElementException exception) { + log.error("{}", exception); + slackService.sendSlackMessage(HttpStatus.INTERNAL_SERVER_ERROR, exception, "error"); + return new ResponseEntity<>( + Api.fail(CommonErrorType.NO_SUCH_ELEMENT), HttpStatus.INTERNAL_SERVER_ERROR); + } + + @ExceptionHandler(IOException.class) + public ResponseEntity> exception(final IOException exception) { + log.error("{}", exception); + slackService.sendSlackMessage(HttpStatus.INTERNAL_SERVER_ERROR, exception, "error"); + return new ResponseEntity<>(Api.fail(CommonErrorType.IO), HttpStatus.INTERNAL_SERVER_ERROR); + } + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity> exception(final IllegalArgumentException exception) { + log.error("{}", exception); + slackService.sendSlackMessage(HttpStatus.INTERNAL_SERVER_ERROR, exception, "error"); + return new ResponseEntity<>( + Api.fail(CommonErrorType.ILLEGAL_ARGUMENT), HttpStatus.INTERNAL_SERVER_ERROR); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity> exception(final Exception exception) { + log.error("{}", exception); + slackService.sendSlackMessage(HttpStatus.INTERNAL_SERVER_ERROR, exception, "error"); + return new ResponseEntity<>( + Api.fail(CommonErrorType.INTERNAL_SERVER), HttpStatus.INTERNAL_SERVER_ERROR); } } diff --git a/src/test/java/org/ioteatime/meonghanyangserver/cctv/controller/CctvControllerTest.java b/src/test/java/org/ioteatime/meonghanyangserver/cctv/controller/CctvControllerTest.java index 5b9d0cf0..e318e160 100644 --- a/src/test/java/org/ioteatime/meonghanyangserver/cctv/controller/CctvControllerTest.java +++ b/src/test/java/org/ioteatime/meonghanyangserver/cctv/controller/CctvControllerTest.java @@ -1,6 +1,6 @@ package org.ioteatime.meonghanyangserver.cctv.controller; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -8,6 +8,8 @@ import org.assertj.core.api.Assertions; import org.ioteatime.meonghanyangserver.cctv.domain.CctvEntity; import org.ioteatime.meonghanyangserver.cctv.repository.CctvRepository; +import org.ioteatime.meonghanyangserver.clients.kvs.KvsClient; +import org.ioteatime.meonghanyangserver.common.type.CommonErrorType; import org.ioteatime.meonghanyangserver.common.utils.JwtUtils; import org.ioteatime.meonghanyangserver.config.ControllerTestConfig; import org.ioteatime.meonghanyangserver.group.domain.GroupEntity; @@ -28,6 +30,7 @@ @SpringBootTest class CctvControllerTest extends ControllerTestConfig { @Autowired private JwtUtils jwtUtils; + @Autowired private KvsClient kvsClient; @Autowired private MemberRepository memberRepository; @Autowired private CctvRepository cctvRepository; @Autowired private GroupRepository groupRepository; @@ -54,7 +57,8 @@ public void beforeEach() { @Test void CCTV를_삭제합니다() throws Exception { - GroupEntity group = GroupEntity.builder().groupName("testgroup").build(); + GroupEntity group = + GroupEntity.builder().groupName("testgroup").fcmTopic("testTopic").build(); groupRepository.save(group); GroupMemberEntity groupMember = @@ -66,17 +70,19 @@ public void beforeEach() { .build(); groupMemberRepository.save(groupMember); + String kvsChannelName = "test-delete-channel"; + CctvEntity cctv = CctvEntity.builder() .group(group) .cctvNickname("test") - .kvsChannelName("test-delete-channel") // KVS에 채널이 존재해야 함 + .kvsChannelName(kvsChannelName) // KVS에 채널이 존재해야 함 .thingId("test-thing-id") .build(); cctvRepository.save(cctv); - String kvsChannelName = cctv.getKvsChannelName(); // KVS에 채널이 존재해야 함 + kvsClient.createSignalingChannel(kvsChannelName); // KVS 채널 생성 mockMvc.perform( delete("/api/cctv/{cctvId}/out", cctv.getId()) @@ -91,4 +97,14 @@ public void beforeEach() { .isEqualTo(false); Assertions.assertThat(cctvRepository.findById(cctv.getId())).isEmpty(); } + + @Test + void 글로벌_예외_처리를_테스트합니다() throws Exception { + // 일치하는 요청 경로가 없는 경우 + mockMvc.perform(get("/open-api/cctv/hello-world")) + .andExpect(status().isInternalServerError()) + .andExpect( + jsonPath("$.result.description") + .value(CommonErrorType.ILLEGAL_ARGUMENT.getDescription())); + } } diff --git a/src/test/resources/application-db.yaml b/src/test/resources/application-db.yaml index d9a343e3..d85e06c9 100644 --- a/src/test/resources/application-db.yaml +++ b/src/test/resources/application-db.yaml @@ -12,7 +12,8 @@ spring: host: ${REDIS_TEST_HOST} port: ${REDIS_TEST_PORT} password: ${REDIS_TEST_PASSWORD} - -logging: - level: - org.springframework.orm.jpa: INFO + batch: + job: + enabled: false + jdbc: + initialize-schema: never diff --git a/src/test/resources/application-key.yaml b/src/test/resources/application-key.yaml index b647138e..c47927a7 100644 --- a/src/test/resources/application-key.yaml +++ b/src/test/resources/application-key.yaml @@ -5,10 +5,21 @@ token: secret: ${JWT_SECRET} google: - service-account: ${GCP_SERVICE_ACCOUNT} official-gmail: ${OFFICIAL_GMAIL} + application-credentials: ${GOOGLE_APPLICATION_CREDENTIALS} aws: - kvs: - access-key: ${AWS_KVS_ACCESS_KEY} - secret-key: ${AWS_KVS_SECRET_KEY} \ No newline at end of file + kvs-access-key: ${AWS_KVS_ACCESS_KEY} + kvs-secret-key: ${AWS_KVS_SECRET_KEY} + ses-access-key: ${AWS_SES_ACCESS_KEY} + ses-secret-key: ${AWS_SES_SECRET_KEY} + iot-access-key: ${AWS_IOT_ACCESS_KEY} + iot-secret-key: ${AWS_IOT_SECRET_KEY} + iot-endpoint: ${AWS_IOT_ENDPOINT} + iot-client-id: ${AWS_IOT_CLIENT_ID} + s3-access-key: ${AWS_S3_ACCESS_KEY} + s3-secret-key: ${AWS_S3_SECRET_KEY} + s3-bucket: ${AWS_S3_BUCKET} + +slack: + key: ${SLACK_BOT_KEY} \ No newline at end of file