diff --git a/ga-batch/src/test/java/com/ga/batch/settlement/calc/CommissionCalculatorTest.java b/ga-batch/src/test/java/com/ga/batch/settlement/calc/CommissionCalculatorTest.java new file mode 100644 index 0000000..6f229e6 --- /dev/null +++ b/ga-batch/src/test/java/com/ga/batch/settlement/calc/CommissionCalculatorTest.java @@ -0,0 +1,160 @@ +package com.ga.batch.settlement.calc; + +import com.ga.core.mapper.org.AgentMapper; +import com.ga.core.mapper.rule.RuleMapper; +import com.ga.core.vo.ledger.LedgerVO; +import com.ga.core.vo.org.AgentVO; +import com.ga.core.vo.product.ContractResp; +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.LocalDate; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class CommissionCalculatorTest { + + @Mock RuleMapper ruleMapper; + @Mock AgentMapper agentMapper; + + @InjectMocks CommissionCalculator calculator; + + private ContractResp contract; + + @BeforeEach + void setUp() { + contract = new ContractResp(); + contract.setContractId(100L); + contract.setAgentId(10L); + contract.setProductId(1L); + contract.setPolicyNo("POL-0001"); + contract.setCompanyCode("SAMSUNG"); + contract.setProductCode("PRD001"); + contract.setInsuranceType("WHOLE_LIFE"); + contract.setPremium(new BigDecimal("1000000")); + contract.setPayCycle("MONTHLY"); + contract.setContractDate(LocalDate.of(2024, 1, 15)); + + AgentVO agent = new AgentVO(); + agent.setAgentId(10L); + agent.setGradeId(5L); + when(agentMapper.selectById(10L)).thenReturn(agent); + } + + @Test + @DisplayName("calcRecruitLedger: premium × company_rate × payout_rate, 세금은 3.3%") + void recruit_basic() { + when(ruleMapper.findCompanyRate(eq(1L), eq(1), any(LocalDate.class))) + .thenReturn(new BigDecimal("7.5")); + when(ruleMapper.findPayoutRate(eq(5L), eq("WHOLE_LIFE"), eq(1), any(LocalDate.class))) + .thenReturn(new BigDecimal("60.0")); + + LedgerVO led = calculator.calcRecruitLedger(contract, "202405"); + + assertThat(led).isNotNull(); + assertThat(led.getCommissionYear()).isEqualTo(1); + assertThat(led.getSettleMonth()).isEqualTo("202405"); + assertThat(led.getStatus()).isEqualTo("CALCULATED"); + assertThat(led.getPremium()).isEqualByComparingTo("1000000"); + assertThat(led.getCompanyRate()).isEqualByComparingTo("7.5"); + assertThat(led.getPayoutRate()).isEqualByComparingTo("60.0"); + // 1,000,000 × 7.5% = 75,000.00 + assertThat(led.getCompanyAmount()).isEqualByComparingTo("75000.00"); + // 75,000 × 60% = 45,000.00 + assertThat(led.getAgentAmount()).isEqualByComparingTo("45000.00"); + // 45,000 × 3.3% = 1,485.00 + assertThat(led.getTaxAmount()).isEqualByComparingTo("1485.00"); + assertThat(led.getContractId()).isEqualTo(100L); + assertThat(led.getAgentId()).isEqualTo(10L); + assertThat(led.getPolicyNo()).isEqualTo("POL-0001"); + assertThat(led.getCompanyCode()).isEqualTo("SAMSUNG"); + assertThat(led.getProductCode()).isEqualTo("PRD001"); + assertThat(led.getInsuranceType()).isEqualTo("WHOLE_LIFE"); + assertThat(led.getPayMethod()).isEqualTo("MONTHLY"); + } + + @Test + @DisplayName("calcMaintainLedger: 회차에 따라 commission_year 가 다름") + void maintain_year3() { + when(ruleMapper.findCompanyRate(eq(1L), eq(3), any(LocalDate.class))) + .thenReturn(new BigDecimal("3.0")); + when(ruleMapper.findPayoutRate(eq(5L), eq("WHOLE_LIFE"), eq(3), any(LocalDate.class))) + .thenReturn(new BigDecimal("50.0")); + + LedgerVO led = calculator.calcMaintainLedger(contract, "202405", 3); + + assertThat(led.getCommissionYear()).isEqualTo(3); + // 1,000,000 × 3% = 30,000 + assertThat(led.getCompanyAmount()).isEqualByComparingTo("30000.00"); + // 30,000 × 50% = 15,000 + assertThat(led.getAgentAmount()).isEqualByComparingTo("15000.00"); + // 15,000 × 3.3% = 495 + assertThat(led.getTaxAmount()).isEqualByComparingTo("495.00"); + } + + @Test + @DisplayName("agent 가 존재하지 않으면 null 반환") + void agentNotFound() { + when(agentMapper.selectById(10L)).thenReturn(null); + + LedgerVO led = calculator.calcRecruitLedger(contract, "202405"); + + assertThat(led).isNull(); + } + + @Test + @DisplayName("규정 누락 시 0 으로 폴백 → 모든 금액 0") + void missingRates_fallbackZero() { + when(ruleMapper.findCompanyRate(any(), any(), any())).thenReturn(null); + when(ruleMapper.findPayoutRate(any(), any(), any(), any())).thenReturn(null); + + LedgerVO led = calculator.calcRecruitLedger(contract, "202405"); + + assertThat(led).isNotNull(); + assertThat(led.getCompanyRate()).isEqualByComparingTo("0"); + assertThat(led.getPayoutRate()).isEqualByComparingTo("0"); + assertThat(led.getCompanyAmount()).isEqualByComparingTo("0.00"); + assertThat(led.getAgentAmount()).isEqualByComparingTo("0.00"); + assertThat(led.getTaxAmount()).isEqualByComparingTo("0.00"); + } + + @Test + @DisplayName("contractDate 가 null 이면 오늘 날짜로 대체 (예외 없음)") + void nullContractDate_usesToday() { + contract.setContractDate(null); + when(ruleMapper.findCompanyRate(eq(1L), eq(1), any(LocalDate.class))) + .thenReturn(new BigDecimal("5")); + when(ruleMapper.findPayoutRate(eq(5L), eq("WHOLE_LIFE"), eq(1), any(LocalDate.class))) + .thenReturn(new BigDecimal("100")); + + LedgerVO led = calculator.calcRecruitLedger(contract, "202405"); + + assertThat(led).isNotNull(); + // 1,000,000 × 5% × 100% = 50,000 + assertThat(led.getAgentAmount()).isEqualByComparingTo("50000.00"); + } + + @Test + @DisplayName("premium 이 null 이면 0 으로 처리 (HALF_UP 0.00)") + void nullPremium() { + contract.setPremium(null); + when(ruleMapper.findCompanyRate(any(), any(), any())).thenReturn(new BigDecimal("7.5")); + when(ruleMapper.findPayoutRate(any(), any(), any(), any())).thenReturn(new BigDecimal("60")); + + LedgerVO led = calculator.calcRecruitLedger(contract, "202405"); + + assertThat(led.getPremium()).isEqualByComparingTo("0"); + assertThat(led.getCompanyAmount()).isEqualByComparingTo("0.00"); + assertThat(led.getAgentAmount()).isEqualByComparingTo("0.00"); + } +} diff --git a/ga-common/src/test/java/com/ga/common/util/DateUtilTest.java b/ga-common/src/test/java/com/ga/common/util/DateUtilTest.java new file mode 100644 index 0000000..57ed0bb --- /dev/null +++ b/ga-common/src/test/java/com/ga/common/util/DateUtilTest.java @@ -0,0 +1,92 @@ +package com.ga.common.util; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class DateUtilTest { + + @Test + @DisplayName("getMonthRange: 평월") + void monthRange_normal() { + String[] r = DateUtil.getMonthRange("202605"); + assertThat(r).containsExactly("2026-05-01", "2026-05-31"); + } + + @Test + @DisplayName("getMonthRange: 윤년 2월") + void monthRange_leap() { + String[] r = DateUtil.getMonthRange("202402"); + assertThat(r).containsExactly("2024-02-01", "2024-02-29"); + } + + @Test + @DisplayName("getMonthRange: 평년 2월") + void monthRange_normalFeb() { + String[] r = DateUtil.getMonthRange("202302"); + assertThat(r).containsExactly("2023-02-01", "2023-02-28"); + } + + @Test + @DisplayName("calcMonthDiff: yyyymm 문자열") + void monthDiff_yyyymm() { + assertThat(DateUtil.calcMonthDiff("202401", "202405")).isEqualTo(4); + assertThat(DateUtil.calcMonthDiff("202312", "202401")).isEqualTo(1); + assertThat(DateUtil.calcMonthDiff("202405", "202401")).isEqualTo(-4); + assertThat(DateUtil.calcMonthDiff("202401", "202401")).isZero(); + } + + @Test + @DisplayName("calcMonthDiff: LocalDate") + void monthDiff_date() { + // 같은 달의 다른 일자는 0개월 + assertThat(DateUtil.calcMonthDiff(LocalDate.of(2024, 1, 1), LocalDate.of(2024, 1, 31))).isZero(); + assertThat(DateUtil.calcMonthDiff(LocalDate.of(2024, 1, 31), LocalDate.of(2024, 2, 1))).isEqualTo(1); + assertThat(DateUtil.calcMonthDiff(LocalDate.of(2023, 1, 1), LocalDate.of(2024, 1, 1))).isEqualTo(12); + } + + @Test + @DisplayName("addMonth: 월 가산/연도 넘김") + void addMonth() { + assertThat(DateUtil.addMonth("202401", 3)).isEqualTo("202404"); + assertThat(DateUtil.addMonth("202312", 1)).isEqualTo("202401"); + assertThat(DateUtil.addMonth("202403", -3)).isEqualTo("202312"); + } + + @Test + @DisplayName("parseDate: 패턴 일치/실패") + void parseDate() { + assertThat(DateUtil.parseDate("2026-05-10", "yyyy-MM-dd")).isEqualTo(LocalDate.of(2026, 5, 10)); + assertThat(DateUtil.parseDate("20260510", "yyyyMMdd")).isEqualTo(LocalDate.of(2026, 5, 10)); + assertThat(DateUtil.parseDate(null, "yyyy-MM-dd")).isNull(); + assertThat(DateUtil.parseDate("", "yyyy-MM-dd")).isNull(); + assertThat(DateUtil.parseDate("invalid", "yyyy-MM-dd")).isNull(); + } + + @Test + @DisplayName("format: 패턴 적용") + void format() { + LocalDate d = LocalDate.of(2026, 5, 10); + assertThat(DateUtil.format(d, "yyyy-MM-dd")).isEqualTo("2026-05-10"); + assertThat(DateUtil.format(d, "yyyyMMdd")).isEqualTo("20260510"); + assertThat(DateUtil.format(null, "yyyy-MM-dd")).isNull(); + } + + @Test + @DisplayName("getMonthRange: 잘못된 yyyymm 은 예외") + void monthRange_invalid() { + assertThatThrownBy(() -> DateUtil.getMonthRange("2026")) + .isInstanceOf(java.time.format.DateTimeParseException.class); + } + + @Test + @DisplayName("getSettleMonth(LocalDate): yyyyMM") + void settleMonthOfDate() { + assertThat(DateUtil.getSettleMonth(LocalDate.of(2026, 5, 10))).isEqualTo("202605"); + assertThat(DateUtil.getSettleMonth(LocalDate.of(2024, 12, 31))).isEqualTo("202412"); + } +} diff --git a/ga-common/src/test/java/com/ga/common/util/EncryptUtilTest.java b/ga-common/src/test/java/com/ga/common/util/EncryptUtilTest.java new file mode 100644 index 0000000..9ceddf8 --- /dev/null +++ b/ga-common/src/test/java/com/ga/common/util/EncryptUtilTest.java @@ -0,0 +1,63 @@ +package com.ga.common.util; + +import com.ga.common.exception.BizException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class EncryptUtilTest { + + private final EncryptUtil util = new EncryptUtil("ga-test-key-1234567890-abcdefghi"); + + @Test + @DisplayName("encrypt → decrypt round-trip") + void roundTrip() { + String plain = "9001011234567"; // 주민번호 형태 + String enc = util.encrypt(plain); + + assertThat(enc).isNotEqualTo(plain); + assertThat(enc).isBase64(); + assertThat(util.decrypt(enc)).isEqualTo(plain); + } + + @Test + @DisplayName("동일 평문 → 동일 암호문 (고정 IV)") + void deterministic() { + String enc1 = util.encrypt("hello"); + String enc2 = util.encrypt("hello"); + assertThat(enc1).isEqualTo(enc2); + } + + @Test + @DisplayName("null/empty 는 그대로 반환") + void nullEmpty() { + assertThat(util.encrypt(null)).isNull(); + assertThat(util.encrypt("")).isEmpty(); + assertThat(util.decrypt(null)).isNull(); + assertThat(util.decrypt("")).isEmpty(); + } + + @Test + @DisplayName("키가 32바이트 미만이면 0 패딩 → 정상 동작") + void shortKey() { + EncryptUtil shortKey = new EncryptUtil("short"); + String enc = shortKey.encrypt("data"); + assertThat(shortKey.decrypt(enc)).isEqualTo("data"); + } + + @Test + @DisplayName("잘못된 암호문 복호화 시 BizException") + void decryptInvalid() { + assertThatThrownBy(() -> util.decrypt("not-base64-!!")) + .isInstanceOf(BizException.class); + } + + @Test + @DisplayName("한글/특수문자 round-trip") + void multiByte() { + String plain = "홍길동@한국#$% áéíóú"; + assertThat(util.decrypt(util.encrypt(plain))).isEqualTo(plain); + } +} diff --git a/ga-common/src/test/java/com/ga/common/util/MaskUtilTest.java b/ga-common/src/test/java/com/ga/common/util/MaskUtilTest.java new file mode 100644 index 0000000..3162f58 --- /dev/null +++ b/ga-common/src/test/java/com/ga/common/util/MaskUtilTest.java @@ -0,0 +1,71 @@ +package com.ga.common.util; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class MaskUtilTest { + + @Test + @DisplayName("maskName: 길이별 분기") + void maskName() { + assertThat(MaskUtil.maskName("홍")).isEqualTo("홍"); // 1자: 그대로 + assertThat(MaskUtil.maskName("홍길")).isEqualTo("홍*"); // 2자: 마지막 + assertThat(MaskUtil.maskName("홍길동")).isEqualTo("홍*동"); // 3자: 가운데 + assertThat(MaskUtil.maskName("홍길동이")).isEqualTo("홍**이"); // 4자 + } + + @Test + @DisplayName("maskPhone: 11자리 / 10자리") + void maskPhone() { + assertThat(MaskUtil.maskPhone("010-1234-5678")).isEqualTo("010-****-5678"); + assertThat(MaskUtil.maskPhone("01012345678")).isEqualTo("010-****-5678"); + assertThat(MaskUtil.maskPhone("031-123-4567")).isEqualTo("031-***-4567"); // 10자리 + assertThat(MaskUtil.maskPhone("02-123-4567")).isEqualTo("02-123-4567"); // 9자리는 원본 + } + + @Test + @DisplayName("maskRrn: 13자리만 마스킹, 그 외 원본") + void maskRrn() { + assertThat(MaskUtil.maskRrn("880101-1234567")).isEqualTo("880101-*******"); + assertThat(MaskUtil.maskRrn("8801011234567")).isEqualTo("880101-*******"); + // 13자리가 아니면 원본 반환 + assertThat(MaskUtil.maskRrn("88010")).isEqualTo("88010"); + } + + @Test + @DisplayName("maskAccount: 하이픈 포함 / 미포함") + void maskAccount() { + assertThat(MaskUtil.maskAccount("110-123-456789")).isEqualTo("110-***-******"); + // 하이픈 없는 경우, 4자리 노출 후 마스킹 + assertThat(MaskUtil.maskAccount("110123456789")).isEqualTo("1101********"); + assertThat(MaskUtil.maskAccount("123")).isEqualTo("123"); // 4자 이하는 원본 + } + + @Test + @DisplayName("maskEmail: 첫 글자만 노출") + void maskEmail() { + assertThat(MaskUtil.maskEmail("aaa@bbb.com")).isEqualTo("a**@bbb.com"); + assertThat(MaskUtil.maskEmail("longusername@domain.io")).isEqualTo("l***********@domain.io"); + // @ 위치가 0 또는 1이면 마스킹 불가 → 원본 + assertThat(MaskUtil.maskEmail("@bbb.com")).isEqualTo("@bbb.com"); + assertThat(MaskUtil.maskEmail("a@bbb.com")).isEqualTo("a@bbb.com"); + } + + @Test + @DisplayName("mask: enum 디스패치") + void mask_enum() { + assertThat(MaskUtil.mask("홍길동", MaskUtil.MaskType.NAME)).isEqualTo("홍*동"); + assertThat(MaskUtil.mask("010-1234-5678", MaskUtil.MaskType.PHONE)).isEqualTo("010-****-5678"); + assertThat(MaskUtil.mask("aaa@bbb.com", MaskUtil.MaskType.EMAIL)).isEqualTo("a**@bbb.com"); + } + + @Test + @DisplayName("mask: null/blank 안전") + void mask_nullSafe() { + assertThat(MaskUtil.mask(null, MaskUtil.MaskType.NAME)).isNull(); + assertThat(MaskUtil.mask("", MaskUtil.MaskType.NAME)).isEmpty(); + assertThat(MaskUtil.mask(" ", MaskUtil.MaskType.NAME)).isEqualTo(" "); + } +} diff --git a/ga-common/src/test/java/com/ga/common/util/MoneyUtilTest.java b/ga-common/src/test/java/com/ga/common/util/MoneyUtilTest.java new file mode 100644 index 0000000..0b2af13 --- /dev/null +++ b/ga-common/src/test/java/com/ga/common/util/MoneyUtilTest.java @@ -0,0 +1,101 @@ +package com.ga.common.util; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; + +import static org.assertj.core.api.Assertions.assertThat; + +class MoneyUtilTest { + + @Test + @DisplayName("zero: null 은 0 으로 치환") + void zero_null() { + assertThat(MoneyUtil.zero(null)).isEqualTo(BigDecimal.ZERO); + assertThat(MoneyUtil.zero(new BigDecimal("12.34"))).isEqualByComparingTo("12.34"); + } + + @Test + @DisplayName("add/sub: null 안전, 정상 합/차") + void add_sub() { + assertThat(MoneyUtil.add(null, new BigDecimal("100"))).isEqualByComparingTo("100"); + assertThat(MoneyUtil.add(new BigDecimal("100"), null)).isEqualByComparingTo("100"); + assertThat(MoneyUtil.add(new BigDecimal("100"), new BigDecimal("50.50"))).isEqualByComparingTo("150.50"); + assertThat(MoneyUtil.sub(new BigDecimal("100"), new BigDecimal("30"))).isEqualByComparingTo("70"); + assertThat(MoneyUtil.sub(null, null)).isEqualByComparingTo("0"); + } + + @Test + @DisplayName("mul: 소수점 2자리 HALF_UP") + void mul_halfUp() { + // 1.235 → 1.24 (HALF_UP) + assertThat(MoneyUtil.mul(new BigDecimal("0.5"), new BigDecimal("2.47"))) + .isEqualByComparingTo("1.24"); + assertThat(MoneyUtil.mul(null, new BigDecimal("100"))).isEqualByComparingTo("0.00"); + } + + @Test + @DisplayName("div: 0 으로 나누면 0 반환, null 안전") + void div_byZero() { + assertThat(MoneyUtil.div(new BigDecimal("100"), BigDecimal.ZERO)).isEqualByComparingTo("0"); + assertThat(MoneyUtil.div(new BigDecimal("100"), null)).isEqualByComparingTo("0"); + assertThat(MoneyUtil.div(new BigDecimal("100"), new BigDecimal("3"))) + .isEqualByComparingTo("33.33"); + } + + @Test + @DisplayName("pct: amount × rate / 100, 2자리 HALF_UP") + void pct_basic() { + // 1,000,000 × 7.5% = 75,000.00 + assertThat(MoneyUtil.pct(new BigDecimal("1000000"), new BigDecimal("7.5"))) + .isEqualByComparingTo("75000.00"); + // 100 × 33.33% = 33.33 + assertThat(MoneyUtil.pct(new BigDecimal("100"), new BigDecimal("33.33"))) + .isEqualByComparingTo("33.33"); + } + + @Test + @DisplayName("pct: null 입력은 0") + void pct_null() { + assertThat(MoneyUtil.pct(null, new BigDecimal("10"))).isEqualByComparingTo("0"); + assertThat(MoneyUtil.pct(new BigDecimal("100"), null)).isEqualByComparingTo("0"); + } + + @Test + @DisplayName("tax: 기본 3.3% 원천징수") + void tax_default() { + // 1,000,000 × 3.3% = 33,000.00 + assertThat(MoneyUtil.tax(new BigDecimal("1000000"))) + .isEqualByComparingTo("33000.00"); + } + + @Test + @DisplayName("tax: 사용자 지정 세율") + void tax_customRate() { + assertThat(MoneyUtil.tax(new BigDecimal("1000"), new BigDecimal("10"))) + .isEqualByComparingTo("100.00"); + } + + @Test + @DisplayName("isZero/isPositive/isNegative") + void signChecks() { + assertThat(MoneyUtil.isZero(null)).isTrue(); + assertThat(MoneyUtil.isZero(BigDecimal.ZERO)).isTrue(); + assertThat(MoneyUtil.isZero(new BigDecimal("0.00"))).isTrue(); + assertThat(MoneyUtil.isPositive(new BigDecimal("0.01"))).isTrue(); + assertThat(MoneyUtil.isPositive(null)).isFalse(); + assertThat(MoneyUtil.isNegative(new BigDecimal("-1"))).isTrue(); + assertThat(MoneyUtil.isNegative(BigDecimal.ZERO)).isFalse(); + } + + @Test + @DisplayName("fmt/fmtDec/fmtSign") + void formatting() { + assertThat(MoneyUtil.fmt(new BigDecimal("1234567"))).isEqualTo("1,234,567"); + assertThat(MoneyUtil.fmt(null)).isEmpty(); + assertThat(MoneyUtil.fmtDec(new BigDecimal("1234.5"))).isEqualTo("1,234.50"); + assertThat(MoneyUtil.fmtSign(new BigDecimal("100"))).isEqualTo("+100"); + assertThat(MoneyUtil.fmtSign(new BigDecimal("-100"))).isEqualTo("-100"); + } +}