카드 매입 승인 경로 Spring Boot 3 전환 #2

Closed
forge-bot wants to merge 11 commits from forge/ACX-E2E-1785394564819-attempt-3-run-6ffc72749088 into main
Showing only changes of commit 3790d30330 - Show all commits

View file

@ -0,0 +1,242 @@
package com.acquirex.approval;
import com.acquirex.approval.domain.ApprovalStatus;
import com.acquirex.approval.dto.ApprovalRequest;
import com.acquirex.approval.dto.ApprovalResponse;
import com.acquirex.approval.service.CardAcquisitionApprovalService;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
/**
* Unit tests for CardAcquisitionApprovalService and Controller.
* Tests Spring Boot 3 migrated approval logic with Jakarta validation.
*/
@WebMvcTest
@DisplayName("Card Acquisition Approval Tests")
@TestMethodOrder(MethodOrderer.DisplayName.class)
class ApprovalTests {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@MockBean
private CardAcquisitionApprovalService approvalService;
// ========== Service Unit Tests ==========
@Nested
@DisplayName("Service: processApproval")
class ServiceProcessApprovalTests {
private CardAcquisitionApprovalService service;
@BeforeEach
void setUp() {
service = new CardAcquisitionApprovalService();
}
@Test
@DisplayName("01 - should approve transaction below threshold")
void shouldApproveTransactionBelowThreshold() {
ApprovalRequest request = new ApprovalRequest("REQ-001", "4111111111111111",
"MERCHANT-001", new BigDecimal("100.00"));
ApprovalResponse response = service.processApproval(request);
assertEquals(ApprovalStatus.APPROVED, response.getStatus());
assertEquals("REQ-001", response.getRequestId());
assertNotNull(response.getProcessedAt());
assertTrue(response.getMessage().contains("approved"));
}
@Test
@DisplayName("02 - should pend transaction above high risk threshold")
void shouldPendTransactionAboveHighRiskThreshold() {
ApprovalRequest request = new ApprovalRequest("REQ-002", "4111111111111111",
"MERCHANT-002", new BigDecimal("600000.00"));
ApprovalResponse response = service.processApproval(request);
assertEquals(ApprovalStatus.PENDING, response.getStatus());
assertTrue(response.getMessage().contains("pending"));
}
@Test
@DisplayName("03 - should reject transaction above max amount")
void shouldRejectTransactionAboveMaxAmount() {
ApprovalRequest request = new ApprovalRequest("REQ-003", "4111111111111111",
"MERCHANT-003", new BigDecimal("1500000.00"));
ApprovalResponse response = service.processApproval(request);
assertEquals(ApprovalStatus.REJECTED, response.getStatus());
assertTrue(response.getMessage().contains("rejected"));
}
}
@Nested
@DisplayName("Service: validateRequest")
class ServiceValidateRequestTests {
private CardAcquisitionApprovalService service;
@BeforeEach
void setUp() {
service = new CardAcquisitionApprovalService();
}
@Test
@DisplayName("04 - should throw exception for invalid card number")
void shouldThrowExceptionForInvalidCardNumber() {
ApprovalRequest request = new ApprovalRequest("REQ-004", "123",
"MERCHANT-001", new BigDecimal("100.00"));
assertThrows(IllegalArgumentException.class, () -> service.validateRequest(request));
}
@Test
@DisplayName("05 - should throw exception for blank merchant ID")
void shouldThrowExceptionForBlankMerchantId() {
ApprovalRequest request = new ApprovalRequest("REQ-005", "4111111111111111",
" ", new BigDecimal("100.00"));
assertThrows(IllegalArgumentException.class, () -> service.validateRequest(request));
}
}
@Nested
@DisplayName("Service: determineApprovalStatus")
class ServiceDetermineStatusTests {
private CardAcquisitionApprovalService service;
@BeforeEach
void setUp() {
service = new CardAcquisitionApprovalService();
}
@Test
@DisplayName("06 - should return APPROVED for amount below threshold")
void shouldReturnApprovedForLowAmount() {
assertEquals(ApprovalStatus.APPROVED, service.determineApprovalStatus(new BigDecimal("100.00")));
}
@Test
@DisplayName("07 - should return PENDING for high risk amount")
void shouldReturnPendingForHighRiskAmount() {
assertEquals(ApprovalStatus.PENDING, service.determineApprovalStatus(new BigDecimal("500001.00")));
}
@Test
@DisplayName("08 - should return REJECTED for amount exceeding max")
void shouldReturnRejectedForExceedingMax() {
assertEquals(ApprovalStatus.REJECTED, service.determineApprovalStatus(new BigDecimal("1000001.00")));
}
}
@Nested
@DisplayName("Service: maskCardNumber")
class ServiceMaskCardNumberTests {
private CardAcquisitionApprovalService service;
@BeforeEach
void setUp() {
service = new CardAcquisitionApprovalService();
}
@Test
@DisplayName("09 - should mask card number correctly")
void shouldMaskCardNumberCorrectly() {
assertEquals("****-****-****-1111", service.maskCardNumber("4111111111111111"));
}
@Test
@DisplayName("10 - should return default mask for short card number")
void shouldReturnDefaultMaskForShortNumber() {
assertEquals("****", service.maskCardNumber("123"));
}
@Test
@DisplayName("11 - should return default mask for null card number")
void shouldReturnDefaultMaskForNull() {
assertEquals("****", service.maskCardNumber(null));
}
}
// ========== Controller Integration Tests ==========
@Nested
@DisplayName("Controller: POST /api/v1/approvals")
class ControllerTests {
@Test
@DisplayName("12 - should return 200 for approved transaction")
void shouldReturn200ForApprovedTransaction() throws Exception {
ApprovalRequest request = new ApprovalRequest("REQ-001", "4111111111111111",
"MERCHANT-001", new BigDecimal("100.00"));
ApprovalResponse response = new ApprovalResponse(1L, "REQ-001", ApprovalStatus.APPROVED,
new BigDecimal("100.00"), LocalDateTime.now(), "Card acquisition approved successfully");
when(approvalService.processApproval(any(ApprovalRequest.class))).thenReturn(response);
mockMvc.perform(post("/api/v1/approvals")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.requestId").value("REQ-001"))
.andExpect(jsonPath("$.status").value("APPROVED"));
}
@Test
@DisplayName("13 - should return 400 for validation error")
void shouldReturn400ForValidationError() throws Exception {
String invalidRequest = "{\"cardNumber\":\"\",\"merchantId\":\"\"}";
mockMvc.perform(post("/api/v1/approvals")
.contentType(MediaType.APPLICATION_JSON)
.content(invalidRequest))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.error").value("Validation Failed"));
}
@Test
@DisplayName("14 - should return 400 for rejected transaction")
void shouldReturn400ForRejectedTransaction() throws Exception {
ApprovalRequest request = new ApprovalRequest("REQ-002", "4111111111111111",
"MERCHANT-002", new BigDecimal("2000000.00"));
ApprovalResponse response = new ApprovalResponse(2L, "REQ-002", ApprovalStatus.REJECTED,
new BigDecimal("2000000.00"), LocalDateTime.now(), "Card acquisition rejected");
when(approvalService.processApproval(any(ApprovalRequest.class))).thenReturn(response);
mockMvc.perform(post("/api/v1/approvals")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.status").value("REJECTED"));
}
@Test
@DisplayName("15 - should return 202 for pending transaction")
void shouldReturn202ForPendingTransaction() throws Exception {
ApprovalRequest request = new ApprovalRequest("REQ-003", "4111111111111111",
"MERCHANT-003", new BigDecimal("600000.00"));
ApprovalResponse response = new ApprovalResponse(3L, "REQ-003", ApprovalStatus.PENDING,
new BigDecimal("600000.00"), LocalDateTime.now(), "Card acquisition pending manual review");
when(approvalService.processApproval(any(ApprovalRequest.class))).thenReturn(response);
mockMvc.perform(post("/api/v1/approvals")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isAccepted())
.andExpect(jsonPath("$.status").value("PENDING"));
}
}
}