카드 정산 업무 규칙과 전환 불변식 추출 #4

Open
forge-bot wants to merge 9 commits from forge/codex-bais-final3-20260713-192718-BAIS-SPRING-ANA-CARD-001-attempt-1-run-4a55ce2ead99 into main
Showing only changes of commit b83a09660f - Show all commits

View file

@ -0,0 +1,193 @@
package com.bais.card.settlement;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import static org.junit.jupiter.api.Assertions.*;
/**
* SettlementCalculator 단위 테스트
* 불변식 검증: BRAND_SETTLEMENT_*, IRD_*, ISSUER_FEE_*, AMOUNT_*, ROUNDING_MODE
*/
@DisplayName("SettlementCalculator 불변식 테스트")
class SettlementCalculatorTest {
private SettlementCalculator calculator;
@BeforeEach
void setUp() {
calculator = new SettlementCalculator();
}
@Nested
@DisplayName("브랜드 정산 불변식 테스트")
class BrandSettlementTests {
@Test
@DisplayName("BRAND_SETTLEMENT_FORMULA: 정산금 = 승인금액 - 취소금액 - 수수료")
void testSettlementFormula() {
BigDecimal approved = new BigDecimal("100000");
BigDecimal cancelled = new BigDecimal("10000");
BigDecimal rate = new BigDecimal("0.02");
SettlementCalculator.BrandSettlementResult result =
calculator.calculateBrandSettlement(approved, cancelled, rate);
assertEquals(new BigDecimal("2000.00"), result.getCommission());
assertEquals(new BigDecimal("88000.00"), result.getSettlementAmount());
}
@Test
@DisplayName("BRAND_SETTLEMENT_NON_NEGATIVE: 정산금은 0 이상")
void testSettlementNonNegative() {
SettlementCalculator.BrandSettlementResult result =
calculator.calculateBrandSettlement(
new BigDecimal("10000"), new BigDecimal("20000"), new BigDecimal("0.02"));
assertEquals(BigDecimal.ZERO.setScale(2), result.getSettlementAmount());
}
@Test
@DisplayName("BRAND_COMMISSION_RATE_DEFAULT: 미지정 시 0.02")
void testDefaultCommissionRate() {
SettlementCalculator.BrandSettlementResult result =
calculator.calculateBrandSettlement(
new BigDecimal("100000"), BigDecimal.ZERO, null);
assertEquals(new BigDecimal("0.02"), result.getCommissionRate());
}
}
@Nested
@DisplayName("IRD 불변식 테스트")
class IrdTests {
@Test
@DisplayName("IRD_FORMULA: IRD = 승인금액 * IRD율")
void testIrdFormula() {
BigDecimal ird = calculator.calculateIRD(
new BigDecimal("100000"), new BigDecimal("0.015"));
assertEquals(new BigDecimal("1500.00"), ird);
}
@Test
@DisplayName("IRD_RATE_DEFAULT: 미지정 시 0.015")
void testDefaultIrdRate() {
BigDecimal ird = calculator.calculateIRD(new BigDecimal("100000"), null);
assertEquals(new BigDecimal("1500.00"), ird);
}
@Test
@DisplayName("IRD_NON_NEGATIVE: IRD는 0 이상")
void testIrdNonNegative() {
BigDecimal ird = calculator.calculateIRD(
new BigDecimal("100000"), new BigDecimal("-0.015"));
assertTrue(ird.compareTo(BigDecimal.ZERO) >= 0);
}
}
@Nested
@DisplayName("발급사 수수료 불변식 테스트")
class IssuerFeeTests {
@Test
@DisplayName("ISSUER_FEE_FORMULA: 발급사 수수료 = IRD * 발급사 수수료율")
void testIssuerFeeFormula() {
BigDecimal fee = calculator.calculateIssuerFee(
new BigDecimal("1500.00"), new BigDecimal("0.005"));
assertEquals(new BigDecimal("7.50"), fee);
}
@Test
@DisplayName("ISSUER_FEE_RATE_DEFAULT: 미지정 시 0.005")
void testDefaultIssuerFeeRate() {
BigDecimal fee = calculator.calculateIssuerFee(new BigDecimal("1500.00"), null);
assertEquals(new BigDecimal("7.50"), fee);
}
@Test
@DisplayName("ISSUER_SETTLEMENT_FORMULA: 발급사 정산금 = IRD - 발급사 수수료")
void testIssuerSettlementFormula() {
SettlementCalculator.IssuerSettlementResult result =
calculator.calculateIssuerSettlement(
new BigDecimal("1500.00"), new BigDecimal("0.005"));
assertEquals(new BigDecimal("7.50"), result.getIssuerFee());
assertEquals(new BigDecimal("1492.50"), result.getIssuerSettlement());
}
}
@Nested
@DisplayName("공통 금액 검증 불변식 테스트")
class AmountValidationTests {
@Test
@DisplayName("AMOUNT_NOT_NULL: null 시 IllegalArgumentException")
void testNullAmount() {
assertThrows(IllegalArgumentException.class, () ->
calculator.calculateBrandSettlement(null, BigDecimal.ZERO, new BigDecimal("0.02")));
}
@Test
@DisplayName("AMOUNT_NON_NEGATIVE: 음수 시 IllegalArgumentException")
void testNegativeAmount() {
assertThrows(IllegalArgumentException.class, () ->
calculator.calculateBrandSettlement(
new BigDecimal("-100000"), BigDecimal.ZERO, new BigDecimal("0.02")));
}
@Test
@DisplayName("AMOUNT_MAX_LIMIT: 최대 한도 초과 시 IllegalArgumentException")
void testAmountExceedsMaxLimit() {
BigDecimal exceedsMax = new BigDecimal("999999999999.99").add(BigDecimal.ONE);
assertThrows(IllegalArgumentException.class, () ->
calculator.calculateBrandSettlement(
exceedsMax, BigDecimal.ZERO, new BigDecimal("0.02")));
}
}
@Nested
@DisplayName("금액 정밀도 불변식 테스트")
class PrecisionTests {
@Test
@DisplayName("AMOUNT_SCALE: 소수점 2자리")
void testAmountScale() {
SettlementCalculator.BrandSettlementResult result =
calculator.calculateBrandSettlement(
new BigDecimal("100000"), BigDecimal.ZERO, new BigDecimal("0.023456"));
assertEquals(2, result.getCommission().scale());
assertEquals(new BigDecimal("2345.60"), result.getCommission());
}
@Test
@DisplayName("ROUNDING_MODE: HALF_UP 반올림")
void testHalfUpRounding() {
SettlementCalculator.BrandSettlementResult result =
calculator.calculateBrandSettlement(
new BigDecimal("100000"), BigDecimal.ZERO, new BigDecimal("0.023455"));
assertEquals(new BigDecimal("2345.50"), result.getCommission());
}
}
@Nested
@DisplayName("전체 정산 흐름 테스트")
class FullSettlementTests {
@Test
@DisplayName("전체 정산 계산 통합 테스트")
void testFullSettlement() {
SettlementCalculator.FullSettlementResult result =
calculator.calculateFullSettlement(
new BigDecimal("100000"),
new BigDecimal("10000"),
new BigDecimal("0.02"),
new BigDecimal("0.015"),
new BigDecimal("0.005"));
assertNotNull(result.getBrandSettlement());
assertNotNull(result.getIrd());
assertNotNull(result.getIssuerSettlement());
assertEquals(new BigDecimal("1500.00"), result.getIrd());
}
}
}