Skip to content

Commit

Permalink
Merge pull request #232 from IoTeaTime/feature/231-global-exception
Browse files Browse the repository at this point in the history
feat: 글로벌 예외 처리 기능 추가
  • Loading branch information
ywonchae1 authored Dec 3, 2024
2 parents 80c1d56 + 0b53e0c commit c5ec498
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -18,22 +20,60 @@
@RestController
@Order(value = Integer.MAX_VALUE)
@RestControllerAdvice
@RequiredArgsConstructor
public class GlobalExceptionHandler {
private final SlackService slackService;

@ExceptionHandler(DataIntegrityViolationException.class)
public ResponseEntity<Api<Object>> 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<Api<Object>> 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<Api<Object>> exception(ApiException exception) {
log.info("", exception);
return ResponseEntity.status(exception.getHttpStatus()).body(Api.fail(exception));
@ExceptionHandler(NullPointerException.class)
public ResponseEntity<Api<Object>> 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<Api<Object>> 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<Api<Object>> 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<Api<Object>> 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<Api<Object>> 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);
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
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;

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;
Expand All @@ -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;
Expand All @@ -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 =
Expand All @@ -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())
Expand All @@ -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()));
}
}
9 changes: 5 additions & 4 deletions src/test/resources/application-db.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
19 changes: 15 additions & 4 deletions src/test/resources/application-key.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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}
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}

0 comments on commit c5ec498

Please sign in to comment.