Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 글로벌 예외 처리 기능 추가 #232

Merged
merged 6 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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}