From 3790d30330dc4bfbb4511013a5e039a92f5e627a Mon Sep 17 00:00:00 2001 From: forge-bot Date: Thu, 30 Jul 2026 08:08:08 +0000 Subject: [PATCH] =?UTF-8?q?=EC=B9=B4=EB=93=9C=20=EB=A7=A4=EC=9E=85=20?= =?UTF-8?q?=EC=8A=B9=EC=9D=B8=20=EA=B2=BD=EB=A1=9C=20Spring=20Boot=203=20?= =?UTF-8?q?=EC=A0=84=ED=99=98=20(ACX-E2E-1785394564819)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/acquirex/approval/ApprovalTests.java | 242 ++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 src/test/java/com/acquirex/approval/ApprovalTests.java diff --git a/src/test/java/com/acquirex/approval/ApprovalTests.java b/src/test/java/com/acquirex/approval/ApprovalTests.java new file mode 100644 index 0000000..4ca8977 --- /dev/null +++ b/src/test/java/com/acquirex/approval/ApprovalTests.java @@ -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")); + } + } +} \ No newline at end of file