From 18fb746222e29cfd4217390bbba0dad74b02f834 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Thu, 30 Jul 2026 08:51:56 +0000 Subject: [PATCH 1/8] =?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 --- pom.xml | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 pom.xml diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..aa38f6d --- /dev/null +++ b/pom.xml @@ -0,0 +1,57 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.2.5 + + + + com.acquirex + card-acquisition + 1.0.0-SNAPSHOT + card-acquisition + 카드 매입 승인 경로 - Spring Boot 3 Migration + + + 17 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-validation + + + com.h2database + h2 + runtime + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + \ No newline at end of file From 1aff9c97c0260722249534d8cf08d5f4482a9e5a Mon Sep 17 00:00:00 2001 From: forge-bot Date: Thu, 30 Jul 2026 08:51:57 +0000 Subject: [PATCH 2/8] =?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 --- .../cardacquisition/CardAcquisitionApplication.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/main/java/com/acquirex/cardacquisition/CardAcquisitionApplication.java diff --git a/src/main/java/com/acquirex/cardacquisition/CardAcquisitionApplication.java b/src/main/java/com/acquirex/cardacquisition/CardAcquisitionApplication.java new file mode 100644 index 0000000..a262683 --- /dev/null +++ b/src/main/java/com/acquirex/cardacquisition/CardAcquisitionApplication.java @@ -0,0 +1,12 @@ +package com.acquirex.cardacquisition; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class CardAcquisitionApplication { + + public static void main(String[] args) { + SpringApplication.run(CardAcquisitionApplication.class, args); + } +} \ No newline at end of file From 855b96cee06fc0332225fcca0ab575f2b934aa96 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Thu, 30 Jul 2026 08:51:58 +0000 Subject: [PATCH 3/8] =?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 --- .../domain/AcquisitionApproval.java | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 src/main/java/com/acquirex/cardacquisition/domain/AcquisitionApproval.java diff --git a/src/main/java/com/acquirex/cardacquisition/domain/AcquisitionApproval.java b/src/main/java/com/acquirex/cardacquisition/domain/AcquisitionApproval.java new file mode 100644 index 0000000..82ad949 --- /dev/null +++ b/src/main/java/com/acquirex/cardacquisition/domain/AcquisitionApproval.java @@ -0,0 +1,68 @@ +package com.acquirex.cardacquisition.domain; + +import jakarta.persistence.*; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Entity +@Table(name = "acquisition_approvals") +public class AcquisitionApproval { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "card_number", nullable = false, length = 16) + private String cardNumber; + + @Column(name = "merchant_id", nullable = false) + private String merchantId; + + @Column(name = "amount", nullable = false, precision = 15, scale = 2) + private BigDecimal amount; + + @Enumerated(EnumType.STRING) + @Column(name = "status", nullable = false) + private ApprovalStatus status; + + @Column(name = "approval_code", length = 12) + private String approvalCode; + + @Column(name = "created_at", nullable = false) + private LocalDateTime createdAt; + + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + public enum ApprovalStatus { + PENDING, APPROVED, REJECTED, CANCELLED + } + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + if (status == null) { + status = ApprovalStatus.PENDING; + } + } + + @PreUpdate + protected void onUpdate() { + updatedAt = LocalDateTime.now(); + } + + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + public String getCardNumber() { return cardNumber; } + public void setCardNumber(String cardNumber) { this.cardNumber = cardNumber; } + public String getMerchantId() { return merchantId; } + public void setMerchantId(String merchantId) { this.merchantId = merchantId; } + public BigDecimal getAmount() { return amount; } + public void setAmount(BigDecimal amount) { this.amount = amount; } + public ApprovalStatus getStatus() { return status; } + public void setStatus(ApprovalStatus status) { this.status = status; } + public String getApprovalCode() { return approvalCode; } + public void setApprovalCode(String approvalCode) { this.approvalCode = approvalCode; } + public LocalDateTime getCreatedAt() { return createdAt; } + public LocalDateTime getUpdatedAt() { return updatedAt; } +} \ No newline at end of file From ae293c9e47520734033d4be32776c24147710a13 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Thu, 30 Jul 2026 08:52:00 +0000 Subject: [PATCH 4/8] =?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 --- .../AcquisitionApprovalRepository.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/main/java/com/acquirex/cardacquisition/repository/AcquisitionApprovalRepository.java diff --git a/src/main/java/com/acquirex/cardacquisition/repository/AcquisitionApprovalRepository.java b/src/main/java/com/acquirex/cardacquisition/repository/AcquisitionApprovalRepository.java new file mode 100644 index 0000000..08be57d --- /dev/null +++ b/src/main/java/com/acquirex/cardacquisition/repository/AcquisitionApprovalRepository.java @@ -0,0 +1,21 @@ +package com.acquirex.cardacquisition.repository; + +import com.acquirex.cardacquisition.domain.AcquisitionApproval; +import com.acquirex.cardacquisition.domain.AcquisitionApproval.ApprovalStatus; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +public interface AcquisitionApprovalRepository extends JpaRepository { + + List findByStatus(ApprovalStatus status); + + List findByMerchantId(String merchantId); + + Optional findByApprovalCode(String approvalCode); + + List findByCardNumberAndStatus(String cardNumber, ApprovalStatus status); +} \ No newline at end of file From 3f27f0740d6f3379bd0dcadcb49fd7670c45b66f Mon Sep 17 00:00:00 2001 From: forge-bot Date: Thu, 30 Jul 2026 08:52:01 +0000 Subject: [PATCH 5/8] =?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 --- .../cardacquisition/dto/ApprovalDtos.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/main/java/com/acquirex/cardacquisition/dto/ApprovalDtos.java diff --git a/src/main/java/com/acquirex/cardacquisition/dto/ApprovalDtos.java b/src/main/java/com/acquirex/cardacquisition/dto/ApprovalDtos.java new file mode 100644 index 0000000..acfd724 --- /dev/null +++ b/src/main/java/com/acquirex/cardacquisition/dto/ApprovalDtos.java @@ -0,0 +1,32 @@ +package com.acquirex.cardacquisition.dto; + +import com.acquirex.cardacquisition.domain.AcquisitionApproval.ApprovalStatus; +import jakarta.validation.constraints.*; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +public sealed interface ApprovalDto permits ApprovalRequest, ApprovalResponse {} + +public record ApprovalRequest( + @NotBlank(message = "카드번호는 필수입니다") + @Size(min = 16, max = 16, message = "카드번호는 16자리입니다") + String cardNumber, + + @NotBlank(message = "가맹점ID는 필수입니다") + String merchantId, + + @NotNull(message = "금액은 필수입니다") + @DecimalMin(value = "0.01", message = "최소 금액은 0.01입니다") + @DecimalMax(value = "999999999.99", message = "최대 금액을 초과했습니다") + BigDecimal amount +) implements ApprovalDto {} + +public record ApprovalResponse( + Long id, + String cardNumber, + String merchantId, + BigDecimal amount, + ApprovalStatus status, + String approvalCode, + LocalDateTime createdAt +) implements ApprovalDto {} \ No newline at end of file From 83fff6fcee268a7e333e915f9cdbaa788f22ea94 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Thu, 30 Jul 2026 08:52:03 +0000 Subject: [PATCH 6/8] =?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 --- .../service/AcquisitionApprovalService.java | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 src/main/java/com/acquirex/cardacquisition/service/AcquisitionApprovalService.java diff --git a/src/main/java/com/acquirex/cardacquisition/service/AcquisitionApprovalService.java b/src/main/java/com/acquirex/cardacquisition/service/AcquisitionApprovalService.java new file mode 100644 index 0000000..0ca5b5e --- /dev/null +++ b/src/main/java/com/acquirex/cardacquisition/service/AcquisitionApprovalService.java @@ -0,0 +1,100 @@ +package com.acquirex.cardacquisition.service; + +import com.acquirex.cardacquisition.domain.AcquisitionApproval; +import com.acquirex.cardacquisition.domain.AcquisitionApproval.ApprovalStatus; +import com.acquirex.cardacquisition.dto.ApprovalDto.ApprovalRequest; +import com.acquirex.cardacquisition.dto.ApprovalDto.ApprovalResponse; +import com.acquirex.cardacquisition.repository.AcquisitionApprovalRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +@Service +public class AcquisitionApprovalService { + + private final AcquisitionApprovalRepository repository; + + public AcquisitionApprovalService(AcquisitionApprovalRepository repository) { + this.repository = repository; + } + + @Transactional + public ApprovalResponse requestApproval(ApprovalRequest request) { + AcquisitionApproval approval = new AcquisitionApproval(); + approval.setCardNumber(request.cardNumber()); + approval.setMerchantId(request.merchantId()); + approval.setAmount(request.amount()); + approval.setStatus(ApprovalStatus.PENDING); + + AcquisitionApproval saved = repository.save(approval); + return toResponse(saved); + } + + @Transactional + public ApprovalResponse approve(Long id) { + AcquisitionApproval approval = repository.findById(id) + .orElseThrow(() -> new IllegalArgumentException("승인 요청을 찾을 수 없습니다: " + id)); + + if (approval.getStatus() != ApprovalStatus.PENDING) { + throw new IllegalStateException("대기 상태의 요청만 승인 가능합니다"); + } + + approval.setStatus(ApprovalStatus.APPROVED); + approval.setApprovalCode(generateApprovalCode()); + + AcquisitionApproval saved = repository.save(approval); + return toResponse(saved); + } + + @Transactional + public ApprovalResponse reject(Long id) { + AcquisitionApproval approval = repository.findById(id) + .orElseThrow(() -> new IllegalArgumentException("승인 요청을 찾을 수 없습니다: " + id)); + + if (approval.getStatus() != ApprovalStatus.PENDING) { + throw new IllegalStateException("대기 상태의 요청만 거절 가능합니다"); + } + + approval.setStatus(ApprovalStatus.REJECTED); + + AcquisitionApproval saved = repository.save(approval); + return toResponse(saved); + } + + @Transactional(readOnly = true) + public List getPendingApprovals() { + return repository.findByStatus(ApprovalStatus.PENDING) + .stream().map(this::toResponse).collect(Collectors.toList()); + } + + @Transactional(readOnly = true) + public ApprovalResponse getApproval(Long id) { + return repository.findById(id) + .map(this::toResponse) + .orElseThrow(() -> new IllegalArgumentException("승인 요청을 찾을 수 없습니다: " + id)); + } + + private String generateApprovalCode() { + return "APX" + UUID.randomUUID().toString().substring(0, 8).toUpperCase(); + } + + private ApprovalResponse toResponse(AcquisitionApproval approval) { + return new ApprovalResponse( + approval.getId(), + maskCardNumber(approval.getCardNumber()), + approval.getMerchantId(), + approval.getAmount(), + approval.getStatus(), + approval.getApprovalCode(), + approval.getCreatedAt() + ); + } + + private String maskCardNumber(String cardNumber) { + if (cardNumber == null || cardNumber.length() < 4) return cardNumber; + return "****-****-****-" + cardNumber.substring(cardNumber.length() - 4); + } +} \ No newline at end of file From c6b3b3d6e73843dfd7756a8bf1755e32dbed9772 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Thu, 30 Jul 2026 08:52:04 +0000 Subject: [PATCH 7/8] =?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 --- .../AcquisitionApprovalController.java | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 src/main/java/com/acquirex/cardacquisition/controller/AcquisitionApprovalController.java diff --git a/src/main/java/com/acquirex/cardacquisition/controller/AcquisitionApprovalController.java b/src/main/java/com/acquirex/cardacquisition/controller/AcquisitionApprovalController.java new file mode 100644 index 0000000..2c7ecb8 --- /dev/null +++ b/src/main/java/com/acquirex/cardacquisition/controller/AcquisitionApprovalController.java @@ -0,0 +1,69 @@ +package com.acquirex.cardacquisition.controller; + +import com.acquirex.cardacquisition.dto.ApprovalDto.ApprovalRequest; +import com.acquirex.cardacquisition.dto.ApprovalDto.ApprovalResponse; +import com.acquirex.cardacquisition.service.AcquisitionApprovalService; +import jakarta.validation.Valid; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/v1/acquisitions") +public class AcquisitionApprovalController { + + private final AcquisitionApprovalService service; + + public AcquisitionApprovalController(AcquisitionApprovalService service) { + this.service = service; + } + + @PostMapping + public ResponseEntity requestApproval(@Valid @RequestBody ApprovalRequest request) { + return ResponseEntity.status(HttpStatus.CREATED).body(service.requestApproval(request)); + } + + @PostMapping("/{id}/approve") + public ResponseEntity approve(@PathVariable Long id) { + return ResponseEntity.ok(service.approve(id)); + } + + @PostMapping("/{id}/reject") + public ResponseEntity reject(@PathVariable Long id) { + return ResponseEntity.ok(service.reject(id)); + } + + @GetMapping("/{id}") + public ResponseEntity getApproval(@PathVariable Long id) { + return ResponseEntity.ok(service.getApproval(id)); + } + + @GetMapping("/pending") + public ResponseEntity> getPendingApprovals() { + return ResponseEntity.ok(service.getPendingApprovals()); + } + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity> handleNotFound(IllegalArgumentException ex) { + return buildError(HttpStatus.NOT_FOUND, ex.getMessage()); + } + + @ExceptionHandler(IllegalStateException.class) + public ResponseEntity> handleConflict(IllegalStateException ex) { + return buildError(HttpStatus.CONFLICT, ex.getMessage()); + } + + private ResponseEntity> buildError(HttpStatus status, String message) { + Map body = new HashMap<>(); + body.put("timestamp", LocalDateTime.now()); + body.put("status", status.value()); + body.put("error", status.getReasonPhrase()); + body.put("message", message); + return ResponseEntity.status(status).body(body); + } +} \ No newline at end of file From 192bec3b97363288caf72df513cb44b7af6bec26 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Thu, 30 Jul 2026 08:52:05 +0000 Subject: [PATCH 8/8] =?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 --- .../AcquisitionApprovalServiceTest.java | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 src/test/java/com/acquirex/cardacquisition/service/AcquisitionApprovalServiceTest.java diff --git a/src/test/java/com/acquirex/cardacquisition/service/AcquisitionApprovalServiceTest.java b/src/test/java/com/acquirex/cardacquisition/service/AcquisitionApprovalServiceTest.java new file mode 100644 index 0000000..b3197c0 --- /dev/null +++ b/src/test/java/com/acquirex/cardacquisition/service/AcquisitionApprovalServiceTest.java @@ -0,0 +1,138 @@ +package com.acquirex.cardacquisition.service; + +import com.acquirex.cardacquisition.domain.AcquisitionApproval; +import com.acquirex.cardacquisition.domain.AcquisitionApproval.ApprovalStatus; +import com.acquirex.cardacquisition.dto.ApprovalDto.ApprovalRequest; +import com.acquirex.cardacquisition.dto.ApprovalDto.ApprovalResponse; +import com.acquirex.cardacquisition.repository.AcquisitionApprovalRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class AcquisitionApprovalServiceTest { + + @Mock + private AcquisitionApprovalRepository repository; + + @InjectMocks + private AcquisitionApprovalService service; + + private ApprovalRequest validRequest; + private AcquisitionApproval pendingApproval; + + @BeforeEach + void setUp() { + validRequest = new ApprovalRequest("1234567890123456", "MERCHANT001", new BigDecimal("50000.00")); + + pendingApproval = new AcquisitionApproval(); + pendingApproval.setId(1L); + pendingApproval.setCardNumber("1234567890123456"); + pendingApproval.setMerchantId("MERCHANT001"); + pendingApproval.setAmount(new BigDecimal("50000.00")); + pendingApproval.setStatus(ApprovalStatus.PENDING); + pendingApproval.setCreatedAt(LocalDateTime.now()); + } + + @Test + @DisplayName("매입 승인 요청 생성 성공") + void requestApproval_Success() { + when(repository.save(any(AcquisitionApproval.class))).thenReturn(pendingApproval); + + ApprovalResponse response = service.requestApproval(validRequest); + + assertThat(response).isNotNull(); + assertThat(response.id()).isEqualTo(1L); + assertThat(response.status()).isEqualTo(ApprovalStatus.PENDING); + assertThat(response.cardNumber()).isEqualTo("****-****-****-3456"); + verify(repository, times(1)).save(any(AcquisitionApproval.class)); + } + + @Test + @DisplayName("매입 승인 처리 성공") + void approve_Success() { + AcquisitionApproval approved = new AcquisitionApproval(); + approved.setId(1L); + approved.setCardNumber("1234567890123456"); + approved.setMerchantId("MERCHANT001"); + approved.setAmount(new BigDecimal("50000.00")); + approved.setStatus(ApprovalStatus.APPROVED); + approved.setApprovalCode("APX12345678"); + approved.setCreatedAt(LocalDateTime.now()); + + when(repository.findById(1L)).thenReturn(Optional.of(pendingApproval)); + when(repository.save(any(AcquisitionApproval.class))).thenReturn(approved); + + ApprovalResponse response = service.approve(1L); + + assertThat(response).isNotNull(); + assertThat(response.status()).isEqualTo(ApprovalStatus.APPROVED); + assertThat(response.approvalCode()).isNotNull(); + assertThat(response.approvalCode()).startsWith("APX"); + } + + @Test + @DisplayName("대기 상태가 아닌 요청은 승인 불가") + void approve_NotPending_ThrowsException() { + pendingApproval.setStatus(ApprovalStatus.APPROVED); + when(repository.findById(1L)).thenReturn(Optional.of(pendingApproval)); + + assertThatThrownBy(() -> service.approve(1L)) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("대기 상태의 요청만 승인 가능"); + } + + @Test + @DisplayName("매입 거절 처리 성공") + void reject_Success() { + AcquisitionApproval rejected = new AcquisitionApproval(); + rejected.setId(1L); + rejected.setCardNumber("1234567890123456"); + rejected.setMerchantId("MERCHANT001"); + rejected.setAmount(new BigDecimal("50000.00")); + rejected.setStatus(ApprovalStatus.REJECTED); + rejected.setCreatedAt(LocalDateTime.now()); + + when(repository.findById(1L)).thenReturn(Optional.of(pendingApproval)); + when(repository.save(any(AcquisitionApproval.class))).thenReturn(rejected); + + ApprovalResponse response = service.reject(1L); + + assertThat(response).isNotNull(); + assertThat(response.status()).isEqualTo(ApprovalStatus.REJECTED); + } + + @Test + @DisplayName("존재하지 않는 요청 조회 시 예외 발생") + void getApproval_NotFound_ThrowsException() { + when(repository.findById(999L)).thenReturn(Optional.empty()); + + assertThatThrownBy(() -> service.getApproval(999L)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("찾을 수 없습니다"); + } + + @Test + @DisplayName("대기 중인 승인 목록 조회") + void getPendingApprovals_Success() { + when(repository.findByStatus(ApprovalStatus.PENDING)).thenReturn(List.of(pendingApproval)); + + List responses = service.getPendingApprovals(); + + assertThat(responses).hasSize(1); + assertThat(responses.get(0).status()).isEqualTo(ApprovalStatus.PENDING); + } +} \ No newline at end of file