Skip to content

Commit

Permalink
Merge pull request #20 from code-review-platform-flow/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
abwarten authored Aug 2, 2024
2 parents 2555782 + 622657f commit b4000a0
Show file tree
Hide file tree
Showing 14 changed files with 365 additions and 30 deletions.
50 changes: 50 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import org.hidetake.gradle.swagger.generator.GenerateSwaggerUI
import org.springframework.boot.gradle.tasks.bundling.BootJar

plugins {
id 'java'
id 'org.springframework.boot' version '3.3.2'
id 'io.spring.dependency-management' version '1.1.6'
id 'com.epages.restdocs-api-spec' version '0.18.2'
id 'org.hidetake.swagger.generator' version '2.19.2'
}

group = 'com.flow'
Expand Down Expand Up @@ -58,9 +63,54 @@ dependencies {
implementation 'com.vladmihalcea:hibernate-types-60:2.21.1'

implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'

testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
testImplementation 'org.springframework.restdocs:spring-restdocs-restassured'
testImplementation 'com.epages:restdocs-api-spec-mockmvc:0.19.2'
swaggerUI 'org.webjars:swagger-ui:4.11.1'
testImplementation 'com.epages:restdocs-api-spec-restassured:0.19.2'
testImplementation 'org.springframework.boot:spring-boot-starter-hateoas'
implementation 'com.h2database:h2'
}

swaggerSources {
sample {
setInputFile(layout.buildDirectory.file("api-spec/openapi3.json").get().asFile)
}
}

openapi {
host = 'localhost:8080'
basePath = '/'
title = 'flow-payment API 문서'
description = 'flow-payment API 문서'
version = '1.0.0'
format = 'json'
}

tasks.named('test') {
useJUnitPlatform()
}

tasks.withType(GenerateSwaggerUI).configureEach {
dependsOn 'openapi3'
}

tasks.register('copySwaggerUI', Copy) {
dependsOn 'generateSwaggerUISample'

def generateSwaggerUISampleTask = tasks.named('generateSwaggerUISample', GenerateSwaggerUI).get()

delete(layout.buildDirectory.file("api-spec/openapi3.json").get().asFile)

from(generateSwaggerUISampleTask.outputDir)
into(layout.buildDirectory.dir("resources/main/static/docs").get().asFile)
}

tasks.withType(BootJar).configureEach {
dependsOn 'copySwaggerUI'
}

tasks.named('resolveMainClassName') {
dependsOn tasks.named('copySwaggerUI')
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import org.springframework.web.server.ResponseStatusException;

import com.flow.payment.common.exception.CustomNotFoundException;
import com.flow.payment.common.property.TossPaymentsProperty;
import com.flow.payment.dto.payment.request.TossPaymentsRequestDto;
import com.flow.payment.dto.payment.response.TossPaymentsResponseDto;
Expand Down Expand Up @@ -39,23 +41,17 @@ public TossPaymentsResponseDto confirm(TossPaymentsRequestDto tossPaymentsReques
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(tossPaymentsRequestDto)
.retrieve()
.onStatus(HttpStatusCode::isError, clientResponse -> handleError(clientResponse))
.onStatus(HttpStatusCode::isError, this::handleError)
.bodyToMono(TossPaymentsResponseDto.class)
.block();

}

private Mono<WebClientResponseException> handleError(ClientResponse clientResponse) {
private Mono<Throwable> handleError(ClientResponse clientResponse) {
return clientResponse.bodyToMono(String.class)
.flatMap(errorBody -> {
log.error("Error response from Toss Payments: {}", errorBody);
return Mono.error(new WebClientResponseException(
"API request failed",
clientResponse.statusCode().value(),
clientResponse.statusCode().toString(),
clientResponse.headers().asHttpHeaders(),
errorBody.getBytes(StandardCharsets.UTF_8),
StandardCharsets.UTF_8));
return Mono.error(new CustomNotFoundException());
});
}

Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.flow.payment.common.exception;

public class CustomNotFoundException extends RuntimeException {
public CustomNotFoundException() {
super();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.flow.payment.common.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(CustomNotFoundException.class)
public ResponseEntity<String> handleOrderNotFoundException(CustomNotFoundException ex) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
@NoArgsConstructor
@AllArgsConstructor
public class OrdersResponseDto {
private Long orderId;
private UUID customerKey;
private String tossOrderId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@
@ToString
@Builder
@EqualsAndHashCode
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class PaymentsConfirmResponseDto {
private String mId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.flow.payment.common.exception.CustomNotFoundException;
import com.flow.payment.dto.order.OrdersDto;
import com.flow.payment.dto.users.UsersDto;
import com.flow.payment.entity.OrdersEntity;
import com.flow.payment.mapper.OrdersMapper;
import com.flow.payment.repository.OrdersRepository;
Expand All @@ -22,7 +22,7 @@ public class OrdersService {

public OrdersDto findOrdersByTossOrderId(UUID tossOrderId) {
return ordersMapper.toDto(ordersRepository.findOrdersByTossOrderId(tossOrderId)
.orElseThrow(() -> new RuntimeException("asdf")));
.orElseThrow(CustomNotFoundException::new));
}

@Transactional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ public PaymentsConfirmResponseDto confirm (PaymentsConfirmRequestDto paymentsCon
.status("결제 성공")
.build();

TossPaymentsResponseDto tossPaymentsResponseDto = tossPaymentsService.postConfirm(tossPaymentsRequest);
tossPaymentsService.postConfirm(tossPaymentsRequest);

paymentsService.save(payments);

return PaymentsConfirmResponseDto.builder().mId(tossPaymentsResponseDto.getMId()).build();
return PaymentsConfirmResponseDto.builder().build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.springframework.stereotype.Service;

import com.flow.payment.common.exception.CustomNotFoundException;
import com.flow.payment.dto.users.UsersDto;
import com.flow.payment.entity.UsersEntity;
import com.flow.payment.mapper.UsersMapper;
Expand All @@ -20,7 +21,7 @@ public class UsersService {

public UsersDto findUsersByEmail(String email) {
return usersMapper.toDto(usersRepository.findUsersByEmail(email)
.orElseThrow(() -> new RuntimeException("User not found with email: " + email)));
.orElseThrow(CustomNotFoundException::new));
}

}
13 changes: 0 additions & 13 deletions src/test/java/com/flow/payment/FlowPaymentApplicationTests.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.flow.payment.config;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.restdocs.RestDocumentationContextProvider;
import org.springframework.restdocs.RestDocumentationExtension;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation;
import org.springframework.restdocs.operation.preprocess.Preprocessors;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.filter.CharacterEncodingFilter;

import com.flow.payment.common.client.TossPaymentsApiAdapter;
import com.flow.payment.controller.OrderController;
import com.flow.payment.controller.PaymentsController;
import com.flow.payment.service.orders.OrdersCreateService;
import com.flow.payment.service.payment.PaymentsConfirmService;
import com.flow.payment.service.payment.TossPaymentsService;

@ExtendWith({RestDocumentationExtension.class})
@WebMvcTest(
controllers = {
PaymentsController.class,
OrderController.class
}
)
@AutoConfigureRestDocs
public abstract class ResourceSnippetIntegrationTest {

protected static final String DEFAULT_RESTDOC_PATH = "{class_name}/{method_name}";

@Autowired
protected MockMvc mockMvc;

@MockBean
protected OrdersCreateService ordersCreateService;

@MockBean
protected PaymentsConfirmService paymentsConfirmService;

@MockBean
protected TossPaymentsService tossPaymentsService;

@BeforeEach
void setUp(final WebApplicationContext context, final RestDocumentationContextProvider provider) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(context)
.apply(MockMvcRestDocumentation.documentationConfiguration(provider)
//요청 body 의 payload 를 보기 좋게 출력
.operationPreprocessors().withRequestDefaults(Preprocessors.prettyPrint())
.and()
//응답 body 의 payload 를 보기 좋게 출력
.operationPreprocessors().withResponseDefaults(Preprocessors.prettyPrint()))
//테스트 결과를 항상 print
.alwaysDo(MockMvcResultHandlers.print())
//한글 깨짐 방지
.addFilter(new CharacterEncodingFilter("UTF-8", true))
.build();
}

}
100 changes: 100 additions & 0 deletions src/test/java/com/flow/payment/order/OrderControllerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package com.flow.payment.order;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders;
import org.springframework.test.web.servlet.ResultActions;

import com.epages.restdocs.apispec.ResourceSnippetParameters;
import com.epages.restdocs.apispec.Schema;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.flow.payment.common.exception.CustomNotFoundException;
import com.flow.payment.config.ResourceSnippetIntegrationTest;
import com.flow.payment.dto.order.request.OrdersRequestDto;
import com.flow.payment.dto.order.response.OrdersResponseDto;

import static com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper.*;
import static com.epages.restdocs.apispec.ResourceDocumentation.*;
import static org.mockito.BDDMockito.*;
import static org.springframework.restdocs.payload.PayloadDocumentation.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import java.math.BigDecimal;
import java.util.UUID;

public class OrderControllerTest extends ResourceSnippetIntegrationTest {

@Test
@DisplayName("주문 생성 성공")
void createOrderSuccess() throws Exception {
OrdersRequestDto request = OrdersRequestDto.builder().email("[email protected]").totalAmount(
BigDecimal.valueOf(1000)).build();
OrdersResponseDto response = OrdersResponseDto.builder()
.customerKey(UUID.randomUUID())
.tossOrderId(UUID.randomUUID().toString())
.build();

given(ordersCreateService.create(request)).willReturn(response);

// when
ResultActions resultActions = mockMvc.perform(
RestDocumentationRequestBuilders.post("/order")
.contentType(MediaType.APPLICATION_JSON)
.content(new ObjectMapper().writeValueAsString(request)));

resultActions
.andExpect(status().isOk())
.andExpect(content().json(new ObjectMapper().writeValueAsString(response)))
.andDo(document(DEFAULT_RESTDOC_PATH,
resource(ResourceSnippetParameters.builder()
.tag("주문")
.summary("주문 생성")
.description("주문 생성 요청 API")
.requestSchema(Schema.schema("주문 생성 요청"))
.responseSchema(Schema.schema("주문 생성 응답"))
.requestFields(
fieldWithPath("email").description("이메일"),
fieldWithPath("totalAmount").description("총 금액")
)
.responseFields(
fieldWithPath("customerKey").description("고객 번호 (UUID)"),
fieldWithPath("tossOrderId").description("토스 주문 번호 (UUID)")
)
.build()
)));
}

@Test
@DisplayName("주문 생성 실패")
void createOrderFailByEmail() throws Exception {
OrdersRequestDto request = OrdersRequestDto.builder().email("[email protected]").totalAmount(
BigDecimal.valueOf(1000)).build();

given(ordersCreateService.create(request)).willThrow(new CustomNotFoundException());

// when
ResultActions resultActions = mockMvc.perform(
RestDocumentationRequestBuilders.post("/order")
.contentType(MediaType.APPLICATION_JSON)
.content(new ObjectMapper().writeValueAsString(request)));

resultActions
.andExpect(status().isNotFound())
.andDo(document(DEFAULT_RESTDOC_PATH,
resource(ResourceSnippetParameters.builder()
.tag("주문")
.summary("주문 생성")
.description("주문 생성 요청 API")
.requestSchema(Schema.schema("주문 생성 요청"))
.responseSchema(Schema.schema("주문 생성 응답"))
.requestFields(
fieldWithPath("email").description("이메일"),
fieldWithPath("totalAmount").description("총 금액")
)
.responseFields()
.build()
)));
}
}
Loading

0 comments on commit b4000a0

Please sign in to comment.