From 46ac1f83a0481c032be7c8f8e6c55c24bcf47e60 Mon Sep 17 00:00:00 2001 From: kswdot Date: Wed, 3 Dec 2025 12:29:58 +0900 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20=EC=9D=BC=ED=9A=8C=EC=84=B1=20?= =?UTF-8?q?=EC=86=A1=EA=B8=88=20=EC=8B=9C=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EA=B2=80=EC=A6=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/OverseasRemittanceRequestDto.java | 8 ++++++++ .../service/OneTimeRemittanceService.java | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/src/main/java/org/creditto/core_banking/domain/overseasremittance/dto/OverseasRemittanceRequestDto.java b/src/main/java/org/creditto/core_banking/domain/overseasremittance/dto/OverseasRemittanceRequestDto.java index 2eb8978..427a430 100644 --- a/src/main/java/org/creditto/core_banking/domain/overseasremittance/dto/OverseasRemittanceRequestDto.java +++ b/src/main/java/org/creditto/core_banking/domain/overseasremittance/dto/OverseasRemittanceRequestDto.java @@ -3,6 +3,7 @@ import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Positive; import lombok.AllArgsConstructor; import lombok.Builder; @@ -29,6 +30,13 @@ public class OverseasRemittanceRequestDto { @NotBlank(message = "출금 계좌번호는 필수입니다.") private String accountNo; + /** + * 출금될 계좌의 비밀번호 + */ + @NotBlank(message = "계좌 비밀번호는 필수입니다.") + @Pattern(regexp = "^\\d{4}$", message = "비밀번호는 4자리 숫자여야 합니다.") + private String password; + /** * 수취인의 상세 정보 */ diff --git a/src/main/java/org/creditto/core_banking/domain/overseasremittance/service/OneTimeRemittanceService.java b/src/main/java/org/creditto/core_banking/domain/overseasremittance/service/OneTimeRemittanceService.java index 7f62e17..e1530db 100644 --- a/src/main/java/org/creditto/core_banking/domain/overseasremittance/service/OneTimeRemittanceService.java +++ b/src/main/java/org/creditto/core_banking/domain/overseasremittance/service/OneTimeRemittanceService.java @@ -3,6 +3,7 @@ import lombok.RequiredArgsConstructor; import org.creditto.core_banking.domain.account.entity.Account; import org.creditto.core_banking.domain.account.repository.AccountRepository; +import org.creditto.core_banking.domain.account.service.AccountService; import org.creditto.core_banking.domain.overseasremittance.dto.ExecuteRemittanceCommand; import org.creditto.core_banking.domain.overseasremittance.dto.OverseasRemittanceRequestDto; import org.creditto.core_banking.domain.overseasremittance.dto.OverseasRemittanceResponseDto; @@ -30,6 +31,7 @@ public class OneTimeRemittanceService { private final AccountRepository accountRepository; private final RecipientFactory recipientFactory; private final OverseasRemittanceRepository overseasRemittanceRepository; + private final AccountService accountService; /** * 클라이언트의 해외송금 요청을 받아 전체 송금 프로세스를 조정합니다. @@ -43,6 +45,9 @@ public OverseasRemittanceResponseDto processRemittance(Long userId, OverseasRemi Account account = accountRepository.findByAccountNo(request.getAccountNo()) .orElseThrow(() -> new CustomBaseException(ErrorBaseCode.NOT_FOUND_ACCOUNT)); + // 비밀번호 검증 + accountService.verifyPassword(account.getId(), request.getPassword()); + // RecipientFactory를 통해 수취인 조회 또는 생성 RecipientCreateDto recipientCreateDto = request.getRecipientInfo().toRecipientCreateDto(); Recipient recipient = recipientFactory.findOrCreate(recipientCreateDto); From cf719bd02b12379bf4c946777395a79e48c4f39b Mon Sep 17 00:00:00 2001 From: kswdot Date: Wed, 3 Dec 2025 12:33:14 +0900 Subject: [PATCH 2/8] =?UTF-8?q?refactor:=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=ED=99=95=EC=9D=B8=20=EA=B2=80=EC=A6=9D=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core_banking/domain/account/dto/AccountCreateReq.java | 6 +----- .../core_banking/domain/account/service/AccountService.java | 5 ----- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/main/java/org/creditto/core_banking/domain/account/dto/AccountCreateReq.java b/src/main/java/org/creditto/core_banking/domain/account/dto/AccountCreateReq.java index 19d38c3..28aae4a 100644 --- a/src/main/java/org/creditto/core_banking/domain/account/dto/AccountCreateReq.java +++ b/src/main/java/org/creditto/core_banking/domain/account/dto/AccountCreateReq.java @@ -11,10 +11,6 @@ public record AccountCreateReq( @NotBlank(message = "비밀번호는 필수입니다.") @Pattern(regexp = "^\\d{4}$", message = "비밀번호는 4자리 숫자여야 합니다.") - String password, - - @NotBlank(message = "비밀번호는 필수입니다.") - @Pattern(regexp = "^\\d{4}$", message = "비밀번호는 4자리 숫자여야 합니다.") - String passwordConfirmation + String password ) { } \ No newline at end of file diff --git a/src/main/java/org/creditto/core_banking/domain/account/service/AccountService.java b/src/main/java/org/creditto/core_banking/domain/account/service/AccountService.java index 1b065e9..52e51d9 100644 --- a/src/main/java/org/creditto/core_banking/domain/account/service/AccountService.java +++ b/src/main/java/org/creditto/core_banking/domain/account/service/AccountService.java @@ -40,11 +40,6 @@ public class AccountService { */ @Transactional public AccountRes createAccount(AccountCreateReq request, Long userId) { - // 비밀번호 일치 확인 - if (!request.password().equals(request.passwordConfirmation())) { - throw new CustomBaseException(ErrorBaseCode.MISMATCH_PASSWORD); - } - // 비밀번호 유효성 검사 passwordValidator.validatePassword(request.password()); From 3169d29d9e2509abbcb04154524cffe0cd5449d1 Mon Sep 17 00:00:00 2001 From: kswdot Date: Wed, 3 Dec 2025 13:57:34 +0900 Subject: [PATCH 3/8] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/account/AccountServiceTest.java | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/src/test/java/org/creditto/core_banking/domain/account/AccountServiceTest.java b/src/test/java/org/creditto/core_banking/domain/account/AccountServiceTest.java index d76bc67..b873f21 100644 --- a/src/test/java/org/creditto/core_banking/domain/account/AccountServiceTest.java +++ b/src/test/java/org/creditto/core_banking/domain/account/AccountServiceTest.java @@ -63,7 +63,6 @@ void createAccount_Success() { AccountCreateReq request = new AccountCreateReq( "새로운 계좌", AccountType.DEPOSIT, - "1234", "1234" ); Long userId = 1L; @@ -96,29 +95,6 @@ void createAccount_Success() { assertThat(capturedAccount.getUserId()).isEqualTo(userId); } - @Test - @DisplayName("계좌 생성 실패 - 비밀번호와 비밀번호 확인 불일치") - void createAccount_Failure_PasswordMismatch() { - // Given - AccountCreateReq request = new AccountCreateReq( - "새로운 계좌", - AccountType.DEPOSIT, - "1234", - "5678" - ); - Long userId = 1L; - - // When & Then - assertThatThrownBy(() -> accountService.createAccount(request, userId)) - .isInstanceOf(CustomBaseException.class) - .extracting("errorCode") - .isEqualTo(ErrorBaseCode.MISMATCH_PASSWORD); - - verify(passwordValidator, never()).validatePassword(anyString()); - verify(passwordEncoder, never()).encode(anyString()); - verify(accountRepository, never()).save(any(Account.class)); - } - @Test @DisplayName("계좌 생성 실패 - 비밀번호 정책 위반") void createAccount_Failure_InvalidPasswordPolicy() { @@ -126,7 +102,6 @@ void createAccount_Failure_InvalidPasswordPolicy() { AccountCreateReq request = new AccountCreateReq( "새로운 계좌", AccountType.DEPOSIT, - "1111", "1111" ); Long userId = 1L; From 70955119505eea7d12c2f72e95d6f8028fa5ddb3 Mon Sep 17 00:00:00 2001 From: kswdot Date: Wed, 3 Dec 2025 14:22:22 +0900 Subject: [PATCH 4/8] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OneTimeRemittanceServiceTest.java | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/test/java/org/creditto/core_banking/domain/overseasremittance/OneTimeRemittanceServiceTest.java b/src/test/java/org/creditto/core_banking/domain/overseasremittance/OneTimeRemittanceServiceTest.java index 8fc6437..e851546 100644 --- a/src/test/java/org/creditto/core_banking/domain/overseasremittance/OneTimeRemittanceServiceTest.java +++ b/src/test/java/org/creditto/core_banking/domain/overseasremittance/OneTimeRemittanceServiceTest.java @@ -2,10 +2,12 @@ import org.creditto.core_banking.domain.account.entity.Account; import org.creditto.core_banking.domain.account.repository.AccountRepository; +import org.creditto.core_banking.domain.account.service.AccountService; import org.creditto.core_banking.domain.overseasremittance.dto.ExecuteRemittanceCommand; import org.creditto.core_banking.domain.overseasremittance.dto.OverseasRemittanceRequestDto; import org.creditto.core_banking.domain.overseasremittance.dto.OverseasRemittanceResponseDto; import org.creditto.core_banking.domain.overseasremittance.entity.RemittanceStatus; +import org.creditto.core_banking.domain.overseasremittance.repository.OverseasRemittanceRepository; // Import 추가 import org.creditto.core_banking.domain.overseasremittance.service.OneTimeRemittanceService; import org.creditto.core_banking.domain.overseasremittance.service.RemittanceProcessorService; import org.creditto.core_banking.domain.recipient.dto.RecipientCreateDto; @@ -29,7 +31,10 @@ import static org.creditto.core_banking.domain.account.entity.AccountState.ACTIVE; import static org.creditto.core_banking.domain.account.entity.AccountType.DEPOSIT; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willDoNothing; import static org.mockito.Mockito.verify; @ExtendWith(MockitoExtension.class) @@ -41,11 +46,14 @@ class OneTimeRemittanceServiceTest { private AccountRepository accountRepository; @Mock private RecipientFactory recipientFactory; + @Mock + private AccountService accountService; + @Mock // 누락되었던 Mock 객체 추가 + private OverseasRemittanceRepository overseasRemittanceRepository; @InjectMocks private OneTimeRemittanceService oneTimeRemittanceService; - // 공통 목 객체 (실제 객체는 아니지만 테스트에 필요한 데이터 제공) private Long userId; private Account mockAccount; private Recipient mockRecipient; @@ -83,8 +91,9 @@ void setUp() { mockRecipient = Recipient.of(mockRecipientCreateDto); baseRequest = OverseasRemittanceRequestDto.builder() - .accountNo(mockAccount.getAccountNo()) // 변경: accountId -> accountNumber - .recipientInfo(mockRecipientInfo) // 변경: recipientId -> RecipientInfo + .accountNo(mockAccount.getAccountNo()) + .password("password123") + .recipientInfo(mockRecipientInfo) .sendCurrency(CurrencyCode.KRW) .targetAmount(BigDecimal.valueOf(10_000)) .startDate(LocalDate.now()) @@ -93,13 +102,13 @@ void setUp() { // Mocking behavior for dependencies given(accountRepository.findByAccountNo(mockAccount.getAccountNo())).willReturn(Optional.of(mockAccount)); given(recipientFactory.findOrCreate(any(RecipientCreateDto.class))).willReturn(mockRecipient); + willDoNothing().given(accountService).verifyPassword(any(), anyString()); } @Test @DisplayName("일회성 송금 처리 성공") void processRemittance_Success() { // given - // RemittanceProcessorService.execute가 성공적으로 반환할 응답 DTO OverseasRemittanceResponseDto mockResponse = OverseasRemittanceResponseDto.builder() .remittanceId(1L) .recipientName("John Doe") @@ -108,7 +117,6 @@ void processRemittance_Success() { .remittanceStatus(RemittanceStatus.COMPLETED) .build(); - // remittanceProcessorService.execute 호출 시 mockResponse를 반환하도록 설정 given(remittanceProcessorService.execute(any(ExecuteRemittanceCommand.class))).willReturn(mockResponse); // when @@ -119,9 +127,7 @@ void processRemittance_Success() { assertThat(result.getRemittanceId()).isEqualTo(1L); assertThat(result.getSendAmount()).isEqualTo(BigDecimal.valueOf(10_000)); assertThat(result.getRecipientName()).isEqualTo("John Doe"); - assertThat(result.getRemittanceStatus()).isEqualTo(RemittanceStatus.COMPLETED); - - // OneTimeRemittanceService가 RecipientFactory와 RemittanceProcessorService.execute를 호출했는지 검증 + verify(accountService).verifyPassword(any(), anyString()); verify(recipientFactory).findOrCreate(any(RecipientCreateDto.class)); verify(remittanceProcessorService).execute(any(ExecuteRemittanceCommand.class)); } @@ -130,18 +136,18 @@ void processRemittance_Success() { @DisplayName("일회성 송금 처리 중 RemittanceProcessorService에서 예외 발생 시 실패") void processRemittance_Fail_When_Processor_Throws_Exception() { // given - // remittanceProcessorService.execute 호출 시 IllegalArgumentException을 던지도록 설정 String errorMessage = "계좌를 찾을 수 없습니다."; given(remittanceProcessorService.execute(any(ExecuteRemittanceCommand.class))) .willThrow(new IllegalArgumentException(errorMessage)); // when & then - // OneTimeRemittanceService가 해당 예외를 그대로 던지는지 검증 assertThatThrownBy(() -> oneTimeRemittanceService.processRemittance(userId, baseRequest)) .isInstanceOf(IllegalArgumentException.class) .hasMessage(errorMessage); + verify(accountService).verifyPassword(any(), anyString()); verify(recipientFactory).findOrCreate(any(RecipientCreateDto.class)); verify(remittanceProcessorService).execute(any(ExecuteRemittanceCommand.class)); } } + From 944d99fdd318a65bb6f6ccc8a755f9705d0dca66 Mon Sep 17 00:00:00 2001 From: kswdot Date: Wed, 3 Dec 2025 14:34:44 +0900 Subject: [PATCH 5/8] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OneTimeRemittanceServiceTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/test/java/org/creditto/core_banking/domain/overseasremittance/OneTimeRemittanceServiceTest.java b/src/test/java/org/creditto/core_banking/domain/overseasremittance/OneTimeRemittanceServiceTest.java index e851546..29a8cce 100644 --- a/src/test/java/org/creditto/core_banking/domain/overseasremittance/OneTimeRemittanceServiceTest.java +++ b/src/test/java/org/creditto/core_banking/domain/overseasremittance/OneTimeRemittanceServiceTest.java @@ -65,6 +65,13 @@ class OneTimeRemittanceServiceTest { void setUp() { userId = 1L; mockAccount = Account.of("1002-123-456789", "예금계좌", "password", BigDecimal.valueOf(600_000), DEPOSIT , ACTIVE, userId); + try { + java.lang.reflect.Field accountIdField = Account.class.getDeclaredField("id"); + accountIdField.setAccessible(true); + accountIdField.set(mockAccount, 1L); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException(e); + } mockRecipientInfo = OverseasRemittanceRequestDto.RecipientInfo.builder() .name("John Doe") @@ -89,6 +96,13 @@ void setUp() { ); mockRecipient = Recipient.of(mockRecipientCreateDto); + try { + java.lang.reflect.Field recipientIdField = Recipient.class.getDeclaredField("recipientId"); + recipientIdField.setAccessible(true); + recipientIdField.set(mockRecipient, 1L); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException(e); + } baseRequest = OverseasRemittanceRequestDto.builder() .accountNo(mockAccount.getAccountNo()) From 38ca752755fb7d7436773781c88af50e84f2425b Mon Sep 17 00:00:00 2001 From: kswdot Date: Wed, 3 Dec 2025 17:18:35 +0900 Subject: [PATCH 6/8] =?UTF-8?q?feat:=20=ED=99=98=EC=A0=84=20=EC=9A=B0?= =?UTF-8?q?=EB=8C=80=EC=9C=A8=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20=EC=9A=B0?= =?UTF-8?q?=EB=8C=80=20=ED=99=98=EC=9C=A8=20=EB=B0=98=ED=99=98=EA=B0=92=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ExchangeController.java | 13 ++--- .../exchange/dto/PreferentialRateRes.java | 7 ++- .../exchange/service/ExchangeService.java | 18 +++++++ .../domain/exchange/ExchangeServiceTest.java | 35 +++++++++++-- .../controller/ExchangeControllerTest.java | 52 +++++++++++++++++++ 5 files changed, 112 insertions(+), 13 deletions(-) create mode 100644 src/test/java/org/creditto/core_banking/domain/exchange/controller/ExchangeControllerTest.java diff --git a/src/main/java/org/creditto/core_banking/domain/exchange/controller/ExchangeController.java b/src/main/java/org/creditto/core_banking/domain/exchange/controller/ExchangeController.java index ab162c4..bb10dda 100644 --- a/src/main/java/org/creditto/core_banking/domain/exchange/controller/ExchangeController.java +++ b/src/main/java/org/creditto/core_banking/domain/exchange/controller/ExchangeController.java @@ -11,8 +11,6 @@ import org.creditto.core_banking.global.common.CurrencyCode; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -48,9 +46,12 @@ public ResponseEntity> getExchangeRate(@Path return ApiResponseUtil.success(SuccessCode.OK, exchangeService.getRateByCurrency(currencyCode)); } - @GetMapping("/preferential-rate/{userId}") - public ResponseEntity> getPreferentialRate(@PathVariable Long userId) { - double rate = creditScoreService.getPreferentialRate(userId); - return ApiResponseUtil.success(SuccessCode.OK, new PreferentialRateRes(rate)); + @GetMapping("/preferential-rate/{userId}/{currency}") + public ResponseEntity> getPreferentialRate( + @PathVariable Long userId, + @PathVariable String currency + ) { + CurrencyCode currencyCode = CurrencyCode.from(currency); + return ApiResponseUtil.success(SuccessCode.OK, exchangeService.getPreferentialRateInfo(userId, currencyCode)); } } diff --git a/src/main/java/org/creditto/core_banking/domain/exchange/dto/PreferentialRateRes.java b/src/main/java/org/creditto/core_banking/domain/exchange/dto/PreferentialRateRes.java index 252c7d3..1687ba2 100644 --- a/src/main/java/org/creditto/core_banking/domain/exchange/dto/PreferentialRateRes.java +++ b/src/main/java/org/creditto/core_banking/domain/exchange/dto/PreferentialRateRes.java @@ -1,6 +1,9 @@ package org.creditto.core_banking.domain.exchange.dto; +import java.math.BigDecimal; + public record PreferentialRateRes( - double preferentialRate + double preferentialRate, + BigDecimal appliedRate ) { -} +} \ No newline at end of file diff --git a/src/main/java/org/creditto/core_banking/domain/exchange/service/ExchangeService.java b/src/main/java/org/creditto/core_banking/domain/exchange/service/ExchangeService.java index b1bf9e0..46e8fe4 100644 --- a/src/main/java/org/creditto/core_banking/domain/exchange/service/ExchangeService.java +++ b/src/main/java/org/creditto/core_banking/domain/exchange/service/ExchangeService.java @@ -7,6 +7,7 @@ import org.creditto.core_banking.domain.exchange.dto.ExchangeRes; import org.creditto.core_banking.domain.exchange.entity.Exchange; import org.creditto.core_banking.domain.exchange.repository.ExchangeRepository; +import org.creditto.core_banking.domain.exchange.dto.PreferentialRateRes; import org.creditto.core_banking.domain.exchange.dto.SingleExchangeRateRes; import org.creditto.core_banking.global.common.CurrencyCode; import org.creditto.core_banking.global.feign.ExchangeRateProvider; @@ -190,6 +191,23 @@ private Exchange saveExchangeHistory(ExchangeReq exchangeReq, BigDecimal fromAmo return exchangeRepository.save(exchange); } + /** + * 우대 환율 및 적용 환율 정보를 조회하여 반환 + * @param userId 사용자 ID + * @param currencyCode 조회할 통화 코드 + * @return 우대 환율 및 적용 환율 정보 + */ + public PreferentialRateRes getPreferentialRateInfo(Long userId, CurrencyCode currencyCode) { + double preferentialRate = creditScoreService.getPreferentialRate(userId); + Map rateMap = getLatestRates(); + BigDecimal baseRateFromApi = getBaseRateForCurrency(rateMap, currencyCode); + BigDecimal adjustedBaseRate = baseRateFromApi.divide(new BigDecimal(currencyCode.getUnit()), ADJUSTED_RATE_SCALE, RoundingMode.HALF_UP); + + BigDecimal appliedRate = calculateAppliedRate(adjustedBaseRate, true, preferentialRate).setScale(2, RoundingMode.HALF_UP); + + return new PreferentialRateRes(preferentialRate, appliedRate); + } + /** * 환율 정보 DTO에서 매매 기준율을 BigDecimal 타입으로 추출 * @param rateMap 전체 환율 맵 diff --git a/src/test/java/org/creditto/core_banking/domain/exchange/ExchangeServiceTest.java b/src/test/java/org/creditto/core_banking/domain/exchange/ExchangeServiceTest.java index d7b33d3..f824c0a 100644 --- a/src/test/java/org/creditto/core_banking/domain/exchange/ExchangeServiceTest.java +++ b/src/test/java/org/creditto/core_banking/domain/exchange/ExchangeServiceTest.java @@ -1,9 +1,6 @@ package org.creditto.core_banking.domain.exchange; -import org.creditto.core_banking.domain.exchange.dto.ExchangeRateRes; -import org.creditto.core_banking.domain.exchange.dto.ExchangeReq; -import org.creditto.core_banking.domain.exchange.dto.ExchangeRes; -import org.creditto.core_banking.domain.exchange.dto.SingleExchangeRateRes; +import org.creditto.core_banking.domain.exchange.dto.*; import org.creditto.core_banking.domain.exchange.entity.Exchange; import org.creditto.core_banking.domain.exchange.repository.ExchangeRepository; import org.creditto.core_banking.domain.exchange.service.ExchangeService; @@ -208,4 +205,32 @@ void getRateByCurrency_NotFound_ThrowsException() { .extracting("errorCode") .isEqualTo(ErrorBaseCode.CURRENCY_NOT_SUPPORTED); } -} \ No newline at end of file + + @Test + @DisplayName("우대 환율 정보 및 적용 환율 조회 성공") + void getPreferentialRateInfo_Success() { + // Given + Long userId = 1L; + CurrencyCode currencyCode = CurrencyCode.USD; + double preferentialRate = MOCK_PREFERENTIAL_RATE.doubleValue(); // 0.5 + + given(creditScoreService.getPreferentialRate(userId)).willReturn(preferentialRate); + given(exchangeRateProvider.getExchangeRates()).willReturn(rateMap); + + // Expected applied rate calculation + BigDecimal baseRate = new BigDecimal(usdRate.getBaseRate()); // 1300.00 + BigDecimal adjustedBaseRate = baseRate.divide(new BigDecimal(currencyCode.getUnit()), 4, RoundingMode.HALF_UP); // 1300.00 / 1 = 1300.00 + BigDecimal effectiveSpread = SPREAD_RATE.multiply(BigDecimal.ONE.subtract(MOCK_PREFERENTIAL_RATE)); // 0.01 * (1 - 0.5) = 0.005 + BigDecimal expectedAppliedRate = adjustedBaseRate.multiply(BigDecimal.ONE.add(effectiveSpread)).setScale(2, RoundingMode.HALF_UP); // 1300.00 * (1 + 0.005) = 1306.50 + + // When + PreferentialRateRes result = exchangeService.getPreferentialRateInfo(userId, currencyCode); + + // Then + assertThat(result).isNotNull(); + assertThat(result.preferentialRate()).isEqualTo(preferentialRate); + assertThat(result.appliedRate()).isEqualByComparingTo(expectedAppliedRate); + } +} + + \ No newline at end of file diff --git a/src/test/java/org/creditto/core_banking/domain/exchange/controller/ExchangeControllerTest.java b/src/test/java/org/creditto/core_banking/domain/exchange/controller/ExchangeControllerTest.java new file mode 100644 index 0000000..caec53b --- /dev/null +++ b/src/test/java/org/creditto/core_banking/domain/exchange/controller/ExchangeControllerTest.java @@ -0,0 +1,52 @@ +package org.creditto.core_banking.domain.exchange.controller; + +import org.creditto.core_banking.domain.creditscore.service.CreditScoreService; +import org.creditto.core_banking.domain.exchange.dto.PreferentialRateRes; +import org.creditto.core_banking.domain.exchange.service.ExchangeService; +import org.creditto.core_banking.global.common.CurrencyCode; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +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.test.web.servlet.MockMvc; + +import java.math.BigDecimal; + +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(ExchangeController.class) +class ExchangeControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private ExchangeService exchangeService; + + @MockBean + private CreditScoreService creditScoreService; + + @Test + @DisplayName("우대 환율 정보 조회 성공") + void getPreferentialRate_Success() throws Exception { + Long userId = 1L; + String currency = "USD"; + double preferentialRate = 0.5; + BigDecimal appliedRate = new BigDecimal("1306.50"); + + PreferentialRateRes mockResponse = new PreferentialRateRes(preferentialRate, appliedRate); + + given(exchangeService.getPreferentialRateInfo(userId, CurrencyCode.USD)).willReturn(mockResponse); + + mockMvc.perform(get("/api/core/exchange/preferential-rate/{userId}/{currency}", userId, currency)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.message").value("요청이 성공했습니다.")) + .andExpect(jsonPath("$.data.preferentialRate").value(preferentialRate)) + .andExpect(jsonPath("$.data.appliedRate").value(appliedRate)); + } +} From 71bd6488b97c3482c352fbeef25ff328d31fd1ea Mon Sep 17 00:00:00 2001 From: kswdot Date: Wed, 3 Dec 2025 17:18:35 +0900 Subject: [PATCH 7/8] =?UTF-8?q?feat:=20=ED=99=98=EC=9C=A8=20=EC=9A=B0?= =?UTF-8?q?=EB=8C=80=20=EC=A0=95=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exchange/controller/ExchangeControllerTest.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/creditto/core_banking/domain/exchange/controller/ExchangeControllerTest.java b/src/test/java/org/creditto/core_banking/domain/exchange/controller/ExchangeControllerTest.java index caec53b..3b86bcf 100644 --- a/src/test/java/org/creditto/core_banking/domain/exchange/controller/ExchangeControllerTest.java +++ b/src/test/java/org/creditto/core_banking/domain/exchange/controller/ExchangeControllerTest.java @@ -7,7 +7,8 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.web.servlet.MockMvc; @@ -18,7 +19,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@WebMvcTest(ExchangeController.class) +@SpringBootTest +@AutoConfigureMockMvc class ExchangeControllerTest { @Autowired @@ -47,6 +49,6 @@ void getPreferentialRate_Success() throws Exception { .andExpect(jsonPath("$.code").value("200")) .andExpect(jsonPath("$.message").value("요청이 성공했습니다.")) .andExpect(jsonPath("$.data.preferentialRate").value(preferentialRate)) - .andExpect(jsonPath("$.data.appliedRate").value(appliedRate)); + .andExpect(jsonPath("$.data.appliedRate").value(appliedRate.doubleValue())); } } From 40182899327afc8d2e5bbdbbbcca324688ca10cf Mon Sep 17 00:00:00 2001 From: kswdot Date: Wed, 3 Dec 2025 17:37:12 +0900 Subject: [PATCH 8/8] =?UTF-8?q?refactor:=20=EC=BD=94=EB=93=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/exchange/ExchangeServiceTest.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/creditto/core_banking/domain/exchange/ExchangeServiceTest.java b/src/test/java/org/creditto/core_banking/domain/exchange/ExchangeServiceTest.java index f824c0a..9ef879e 100644 --- a/src/test/java/org/creditto/core_banking/domain/exchange/ExchangeServiceTest.java +++ b/src/test/java/org/creditto/core_banking/domain/exchange/ExchangeServiceTest.java @@ -1,6 +1,10 @@ package org.creditto.core_banking.domain.exchange; -import org.creditto.core_banking.domain.exchange.dto.*; +import org.creditto.core_banking.domain.exchange.dto.ExchangeRateRes; +import org.creditto.core_banking.domain.exchange.dto.ExchangeReq; +import org.creditto.core_banking.domain.exchange.dto.ExchangeRes; +import org.creditto.core_banking.domain.exchange.dto.PreferentialRateRes; +import org.creditto.core_banking.domain.exchange.dto.SingleExchangeRateRes; import org.creditto.core_banking.domain.exchange.entity.Exchange; import org.creditto.core_banking.domain.exchange.repository.ExchangeRepository; import org.creditto.core_banking.domain.exchange.service.ExchangeService; @@ -231,6 +235,4 @@ void getPreferentialRateInfo_Success() { assertThat(result.preferentialRate()).isEqualTo(preferentialRate); assertThat(result.appliedRate()).isEqualByComparingTo(expectedAppliedRate); } -} - - \ No newline at end of file +} \ No newline at end of file