test: ga-common 유틸 + ga-batch CommissionCalculator 단위 테스트 39건

- MoneyUtilTest (10): pct/tax/mul/div HALF_UP, null 안전성
- DateUtilTest (10): 정산월 범위, 윤년/평년, 월 차이/가산
- MaskUtilTest (7): NAME/PHONE/RRN/ACCOUNT/EMAIL 마스킹 규칙
- EncryptUtilTest (6): AES-256 round-trip, 한글, 짧은 키 패딩
- CommissionCalculatorTest (6): 모집/유지수수료 계산 공식, 폴백, agent 누락

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
GA Pro
2026-05-10 20:47:47 +09:00
parent aee9c2c2ca
commit 8a0483cccc
5 changed files with 487 additions and 0 deletions
@@ -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");
}
}
@@ -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");
}
}
@@ -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);
}
}
@@ -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(" ");
}
}
@@ -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");
}
}