Skip to content

Commit

Permalink
Refactor: 외부 API 통신 예외를 추가한다.
Browse files Browse the repository at this point in the history
Refactor: 외부 API 통신 예외를 추가한다.
  • Loading branch information
hseong3243 authored May 19, 2024
2 parents aa762bd + c10c5f3 commit 6c52814
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 8 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ dependencies {
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'com.squareup.okhttp3:mockwebserver:4.12.0'

testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.seong.shoutlink.global.client.api;

public class ApiException extends RuntimeException{

public ApiException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,28 @@
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.seong.shoutlink.domain.common.ApiClient;
import com.seong.shoutlink.domain.exception.ErrorCode;
import com.seong.shoutlink.domain.exception.ShoutLinkException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import org.springframework.http.ResponseEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatusCode;
import org.springframework.web.client.RestClient;

@Slf4j
public class RestApiClient implements ApiClient {

private final RestClient restClient;
private final ObjectMapper objectMapper;

public RestApiClient(ObjectMapper objectMapper) {
restClient = RestClient.create();
restClient = RestClient.builder()
.defaultStatusHandler(HttpStatusCode::is5xxServerError, (request, response) -> {
log.error("[API] API 서버 에러가 발생하였습니다. [response - status={}, body={}]",
response.getStatusCode(),
new String(response.getBody().readAllBytes(), StandardCharsets.UTF_8));
throw new ApiException("API 서버 에러가 발생하였습니다.");
})
.build();
this.objectMapper = objectMapper;
}

Expand All @@ -27,17 +35,26 @@ public Map<String, Object> post(
Map<String, List<String>> uriVariables,
Map<String, List<String>> headers,
String requestBody) {
ResponseEntity<String> entity = restClient.post()
String responseBody = restClient.post()
.uri(url, uriVariables)
.headers(httpHeaders -> httpHeaders.putAll(headers))
.body(requestBody)
.retrieve()
.toEntity(String.class);
.onStatus(HttpStatusCode::is4xxClientError, (request, response) -> {
log.error("[API] 요청 형식이 잘못되었습니다. "
+ "[request - url={}, uriVariables={}, headers={}, body={}] "
+ "[response - body={}",
url, uriVariables, headers, requestBody,
new String(response.getBody().readAllBytes(), StandardCharsets.UTF_8));
throw new ApiException("요청 형식이 잘못되었습니다.");
})
.body(String.class);

try {
return objectMapper.readValue(entity.getBody(), new TypeReference<>() {});
return objectMapper.readValue(responseBody, new TypeReference<>() {});
} catch (JsonProcessingException e) {
throw new ShoutLinkException("API 응답을 읽는데 실패하였습니다.", ErrorCode.ILLEGAL_ARGUMENT);
log.error("[API] API 응답을 읽는데 실패하였습니다. [response - body={}]", responseBody);
throw new ApiException("API 응답을 읽는데 실패하였습니다.");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package com.seong.shoutlink.global.client.api;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchException;

import com.seong.shoutlink.base.BaseIntegrationTest;
import java.io.IOException;
import java.util.Map;
import okhttp3.HttpUrl;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

class RestApiClientTest extends BaseIntegrationTest {

@Autowired
private RestApiClient restApiClient;

MockWebServer mockServer;

@BeforeEach
void setUp() throws IOException {
mockServer = new MockWebServer();
mockServer.start();
}

@AfterEach
void tearDown() throws IOException {
mockServer.shutdown();
}

@Nested
@DisplayName("생성하면")
class CreateTest {

@Test
@DisplayName("예외(apiException): 5xx 에러일 때")
void whenStatusCode_5xx() {
//given
MockResponse mockResponse = new MockResponse()
.setResponseCode(500);
mockServer.enqueue(mockResponse);
HttpUrl baseUrl = mockServer.url("/server-error");

//when
Exception exception = catchException(
() -> restApiClient.post(baseUrl.toString(), Map.of(), Map.of(), ""));

//then
assertThat(exception).isInstanceOf(ApiException.class);
}
}

@Nested
@DisplayName("post 호출 시")
class WhenPost {

@Test
@DisplayName("성공")
void post() {
//given
MockResponse mockResponse = new MockResponse()
.addHeader("Content-Type", "application/json")
.setBody("{\"content\": \"hello\"}");
mockServer.enqueue(mockResponse);
HttpUrl baseUrl = mockServer.url("/success");

//when
Map<String, Object> response = restApiClient.post(baseUrl.toString(), Map.of(),
Map.of(), "");

//then
assertThat(response.get("content")).isEqualTo("hello");
}

@Test
@DisplayName("예외(apiException): 4xx 에러일 때")
void whenStatusCode_4xx() {
//given
MockResponse mockResponse = new MockResponse()
.setResponseCode(400);
mockServer.enqueue(mockResponse);
HttpUrl baseUrl = mockServer.url("/client-error");

//when
Exception exception = catchException(
() -> restApiClient.post(baseUrl.toString(), Map.of(), Map.of(), ""));

//then
assertThat(exception).isInstanceOf(ApiException.class);
}

@Test
@DisplayName("예외(apiException): 응답 형식이 json이 아닐 때")
void whenResponse_isNotJson() {
//given
MockResponse mockResponse = new MockResponse()
.setBody("not json");
mockServer.enqueue(mockResponse);
HttpUrl baseUrl = mockServer.url("/not-json");

//when
Exception exception = catchException(
() -> restApiClient.post(baseUrl.toString(), Map.of(), Map.of(), ""));

//then
assertThat(exception).isInstanceOf(ApiException.class);
}
}
}

0 comments on commit 6c52814

Please sign in to comment.