마스킹 유틸리티 추가 (MaskingUtils + 단위테스트)

- 보안아키텍처 마스킹 정책에 따라 고유식별정보·금융식별정보·인증정보·신상정보·온라인정보
- 카테고리별 마스킹 처리 유틸 및 JUnit 5 단위테스트(3단계 번호 체계) 신규 작성
This commit is contained in:
curry772
2026-05-20 16:33:21 +09:00
parent 8d63586749
commit 6eedac51f7
2 changed files with 1021 additions and 0 deletions
@@ -0,0 +1,581 @@
package com.eactive.eai.common.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
@TestMethodOrder(MethodOrderer.DisplayName.class)
class MaskingUtilsTest {
// =========================================================================
// 1. 고유식별정보
// =========================================================================
// --- 1-1. 주민등록번호 ---
@Test
@DisplayName("1-1-1. 주민등록번호 — 하이픈 포함 정상 형식")
void testResidentNumber_withHyphen() {
String input = "123456-1234567";
String result = MaskingUtils.maskResidentNumber(input);
assertEquals("123456-1******", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("1-1-2. 주민등록번호 — 하이픈 없는 형식 (뒤 6자리 마스킹)")
void testResidentNumber_withoutHyphen() {
String input = "0701011234567";
String result = MaskingUtils.maskResidentNumber(input);
assertEquals("0701011******", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("1-1-3. 주민등록번호 — null/빈 문자열 방어")
void testResidentNumber_nullAndEmpty() {
assertNull(MaskingUtils.maskResidentNumber(null));
assertEquals("", MaskingUtils.maskResidentNumber(""));
}
// --- 1-2. 운전면허번호 ---
@Test
@DisplayName("1-2-1. 운전면허번호 — 숫자 7자리 역방향 마스킹")
void testDriverLicense_normal() {
String input = "제주-22-300001-50";
String result = MaskingUtils.maskDriverLicense(input);
assertEquals("제주-22-3*****-**", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("1-2-2. 운전면허번호 — null/빈 문자열 방어")
void testDriverLicense_nullAndEmpty() {
assertNull(MaskingUtils.maskDriverLicense(null));
assertEquals("", MaskingUtils.maskDriverLicense(""));
}
// --- 1-3. 여권번호 ---
@Test
@DisplayName("1-3-1. 여권번호 — 구여권 형식")
void testPassport_old() {
String input = "M12345678";
String result = MaskingUtils.maskPassport(input);
assertEquals("M1234****", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("1-3-2. 여권번호 — 신여권 형식 (영문+숫자 혼합)")
void testPassport_new() {
String input = "M123A5678";
String result = MaskingUtils.maskPassport(input);
assertEquals("M123A****", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("1-3-3. 여권번호 — null/빈 문자열 방어")
void testPassport_nullAndEmpty() {
assertNull(MaskingUtils.maskPassport(null));
assertEquals("", MaskingUtils.maskPassport(""));
}
// --- 1-4. 외국인등록번호 ---
@Test
@DisplayName("1-4-1. 외국인등록번호 — 주민등록번호와 동일 방식")
void testForeignerNumber_normal() {
String input = "123456-5234567";
String result = MaskingUtils.maskForeignerNumber(input);
assertEquals("123456-5******", result);
assertEquals(input.length(), result.length());
}
// =========================================================================
// 2. 금융식별정보
// =========================================================================
// --- 2-1. 계좌번호 ---
@Test
@DisplayName("2-1-1. 계좌번호 — 명세 예시 형식 (XX-XX-XXXXXX)")
void testAccountNumber_specExample() {
String input = "99-94-123451";
String result = MaskingUtils.maskAccountNumber(input);
assertEquals("99-9*-*****1", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("2-1-2. 계좌번호 — 일반 형식 (XX-XXXXXX-X): 하이픈 원위치 유지")
void testAccountNumber_commonFormat() {
String input = "99-912345-1";
String result = MaskingUtils.maskAccountNumber(input);
assertEquals("99-9*****-1", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("2-1-3. 계좌번호 — 하이픈 없는 형식")
void testAccountNumber_noHyphen() {
String input = "123456789";
String result = MaskingUtils.maskAccountNumber(input);
assertEquals("123*****9", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("2-1-4. 계좌번호 — 4자리 이하는 마스킹 안 함")
void testAccountNumber_tooShort() {
assertEquals("1234", MaskingUtils.maskAccountNumber("1234"));
}
@Test
@DisplayName("2-1-5. 계좌번호 — null/빈 문자열 방어")
void testAccountNumber_nullAndEmpty() {
assertNull(MaskingUtils.maskAccountNumber(null));
assertEquals("", MaskingUtils.maskAccountNumber(""));
}
// --- 2-2. 카드번호 ---
@Test
@DisplayName("2-2-1. 카드번호 — PCI-DSS 7~12번째 자리 마스킹")
void testCardNumber_normal() {
String input = "4518-4212-3456-1234";
String result = MaskingUtils.maskCardNumber(input);
assertEquals("4518-42**-****-1234", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("2-2-2. 카드번호 — 하이픈 없는 16자리 형식")
void testCardNumber_noHyphen() {
String input = "4518421234561234";
String result = MaskingUtils.maskCardNumber(input);
assertEquals("451842******1234", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("2-2-3. 카드번호 — null/빈 문자열 방어")
void testCardNumber_nullAndEmpty() {
assertNull(MaskingUtils.maskCardNumber(null));
assertEquals("", MaskingUtils.maskCardNumber(""));
}
// --- 2-3. 카드 유효기간 ---
@Test
@DisplayName("2-3-1. 카드 유효기간 — 월/년 전체 마스킹")
void testCardExpiry_normal() {
String input = "12/25";
String result = MaskingUtils.maskCardExpiry(input);
assertEquals("**/**", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("2-3-2. 카드 유효기간 — 구분자 없는 형식")
void testCardExpiry_noDelimiter() {
String input = "1225";
String result = MaskingUtils.maskCardExpiry(input);
assertEquals("****", result);
assertEquals(input.length(), result.length());
}
// --- 2-4. CVV ---
@Test
@DisplayName("2-4-1. CVV — 전체 마스킹")
void testCvv_normal() {
String input = "123";
String result = MaskingUtils.maskCvv(input);
assertEquals("***", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("2-4-2. CVV — null/빈 문자열 방어")
void testCvv_nullAndEmpty() {
assertNull(MaskingUtils.maskCvv(null));
assertEquals("", MaskingUtils.maskCvv(""));
}
// =========================================================================
// 3. 인증정보
// =========================================================================
// --- 3-1. 비밀번호 ---
@Test
@DisplayName("3-1-1. 비밀번호 — 전체 자리 마스킹")
void testPassword_normal() {
String input = "password";
String result = MaskingUtils.maskPassword(input);
assertEquals("********", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("3-1-2. 비밀번호 — 숫자/특수문자 혼합")
void testPassword_mixed() {
String input = "Pass1234!@";
String result = MaskingUtils.maskPassword(input);
assertEquals("**********", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("3-1-3. 비밀번호 — null/빈 문자열 방어")
void testPassword_nullAndEmpty() {
assertNull(MaskingUtils.maskPassword(null));
assertEquals("", MaskingUtils.maskPassword(""));
}
// =========================================================================
// 4. 신상정보
// =========================================================================
// --- 4-1. 전화번호/휴대폰 ---
@Test
@DisplayName("4-1-1. 전화번호 — 가운데·뒷자리 각 2자리 마스킹 (지역)")
void testPhoneNumber_local() {
String input = "064-1234-5678";
String result = MaskingUtils.maskPhoneNumber(input);
assertEquals("064-12**-56**", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("4-1-2. 전화번호 — 서울 02 형식")
void testPhoneNumber_seoul() {
String input = "02-1234-5678";
String result = MaskingUtils.maskPhoneNumber(input);
assertEquals("02-12**-56**", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("4-1-3. 전화번호 — 010 형식 (하이픈 있음)")
void testMobileNumber_normal() {
String input = "010-1234-5678";
String result = MaskingUtils.maskMobileNumber(input);
assertEquals("010-12**-56**", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("4-1-4. 전화번호 — 하이픈 없는 11자리 휴대폰")
void testPhoneNumber_noHyphen_mobile() {
String input = "01012345678";
String result = MaskingUtils.maskPhoneNumber(input);
assertEquals("01012**56**", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("4-1-5. 전화번호 — 하이픈 없는 10자리 서울(02)")
void testPhoneNumber_noHyphen_seoul10() {
String input = "0212345678";
String result = MaskingUtils.maskPhoneNumber(input);
assertEquals("0212**56**", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("4-1-6. 전화번호 — 하이픈 없는 9자리 서울(02)")
void testPhoneNumber_noHyphen_seoul9() {
String input = "021234567";
String result = MaskingUtils.maskPhoneNumber(input);
assertEquals("021**45**", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("4-1-7. 전화번호 — 하이픈 없는 10자리 지역(0XX-XXX-XXXX)")
void testPhoneNumber_noHyphen_local10() {
String input = "0641234567";
String result = MaskingUtils.maskPhoneNumber(input);
assertEquals("0641**45**", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("4-1-8. 전화번호 — 하이픈 없는 11자리 지역(0XX-XXXX-XXXX)")
void testPhoneNumber_noHyphen_local11() {
String input = "06412345678";
String result = MaskingUtils.maskPhoneNumber(input);
assertEquals("06412**56**", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("4-1-9. 전화번호 — 인식 불가 형식은 원본 반환")
void testPhoneNumber_invalidFormat() {
assertEquals("0641234", MaskingUtils.maskPhoneNumber("0641234"));
}
@Test
@DisplayName("4-1-10. 전화번호 — null/빈 문자열 방어")
void testPhoneNumber_nullAndEmpty() {
assertNull(MaskingUtils.maskPhoneNumber(null));
assertEquals("", MaskingUtils.maskPhoneNumber(""));
}
// --- 4-2. 이메일 ---
@Test
@DisplayName("4-2-1. 이메일 — 앞 2자리·@ 앞 2자리 마스킹")
void testEmail_normal() {
String input = "aajjbacc@shinhan.com";
String result = MaskingUtils.maskEmail(input);
assertEquals("**jjba**@shinhan.com", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("4-2-2. 이메일 — 로컬 파트 4자 이하는 전체 마스킹")
void testEmail_shortLocal() {
String input = "abc@test.com";
String result = MaskingUtils.maskEmail(input);
assertEquals("***@test.com", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("4-2-3. 이메일 — @ 없는 경우 원본 반환")
void testEmail_noAtSign() {
assertEquals("notanemail", MaskingUtils.maskEmail("notanemail"));
}
@Test
@DisplayName("4-2-4. 이메일 — null/빈 문자열 방어")
void testEmail_nullAndEmpty() {
assertNull(MaskingUtils.maskEmail(null));
assertEquals("", MaskingUtils.maskEmail(""));
}
// --- 4-3. 한글 성명 ---
@Test
@DisplayName("4-3-1. 한글 성명 — 2자: 뒤 1자리 마스킹")
void testKoreanName_2chars() {
String input = "홍동";
String result = MaskingUtils.maskKoreanName(input);
assertEquals("홍*", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("4-3-2. 한글 성명 — 3자: 가운데 1자리 마스킹")
void testKoreanName_3chars() {
String input = "홍길동";
String result = MaskingUtils.maskKoreanName(input);
assertEquals("홍*동", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("4-3-3. 한글 성명 — 4자: 앞뒤 1자 제외 가운데 전체 마스킹")
void testKoreanName_4chars() {
String input = "홍길순동";
String result = MaskingUtils.maskKoreanName(input);
assertEquals("홍**동", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("4-3-4. 한글 성명 — 한자 2자 성명")
void testKoreanName_hanja() {
String input = "田中";
String result = MaskingUtils.maskKoreanName(input);
assertEquals("田*", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("4-3-5. 한글 성명 — 1자는 마스킹 없이 반환")
void testKoreanName_1char() {
String input = "";
String result = MaskingUtils.maskKoreanName(input);
assertEquals("", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("4-3-6. 한글 성명 — null/빈 문자열 방어")
void testKoreanName_nullAndEmpty() {
assertNull(MaskingUtils.maskKoreanName(null));
assertEquals("", MaskingUtils.maskKoreanName(""));
}
// --- 4-4. 영문 성명 ---
@Test
@DisplayName("4-4-1. 영문 성명 — 이름(first name) 마스킹·성(last name) 노출")
void testEnglishName_normal() {
String input = "GILDONG HONG";
String result = MaskingUtils.maskEnglishName(input);
assertEquals("******* HONG", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("4-4-2. 영문 성명 — 이름이 2개인 경우")
void testEnglishName_multipleFirstNames() {
String input = "MARY GRACE HONG";
String result = MaskingUtils.maskEnglishName(input);
assertEquals("**** ***** HONG", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("4-4-3. 영문 성명 — 단일 토큰은 마스킹 없이 반환")
void testEnglishName_singleToken() {
assertEquals("HONG", MaskingUtils.maskEnglishName("HONG"));
}
@Test
@DisplayName("4-4-4. 영문 성명 — null/빈 문자열 방어")
void testEnglishName_nullAndEmpty() {
assertNull(MaskingUtils.maskEnglishName(null));
assertEquals("", MaskingUtils.maskEnglishName(""));
}
// --- 4-5. 지번주소 ---
@Test
@DisplayName("4-5-1. 지번주소 — 번지 마스킹 및 상세주소 전체 마스킹")
void testJibunAddress_normal() {
String input = "제주특별자치도 제주시 연동 123번지, 101동 202호";
String result = MaskingUtils.maskJibunAddress(input);
assertEquals("제주특별자치도 제주시 연동 ***번지, **** ****", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("4-5-2. 지번주소 — 상세주소(쉼표 이후) 없는 경우")
void testJibunAddress_noDetail() {
String input = "제주특별자치도 제주시 연동 123번지";
String result = MaskingUtils.maskJibunAddress(input);
assertEquals("제주특별자치도 제주시 연동 ***번지", result);
assertEquals(input.length(), result.length());
}
// --- 4-6. 도로명주소 ---
@Test
@DisplayName("4-6-1. 도로명주소 — 도로명 번호 및 번지·상세주소 마스킹 (자릿수 보존)")
void testRoadAddress_normal() {
String input = "제주특별자치도 제주시 은남2길 5, 101동 202호";
String result = MaskingUtils.maskRoadAddress(input);
assertEquals("제주특별자치도 제주시 은남*길 *, **** ****", result);
assertEquals(input.length(), result.length());
}
@Test
@DisplayName("4-6-2. 도로명주소 — 상세주소(쉼표 이후) 없는 경우")
void testRoadAddress_noDetail() {
String input = "제주특별자치도 제주시 은남2길 5";
String result = MaskingUtils.maskRoadAddress(input);
assertEquals("제주특별자치도 제주시 은남*길 *", result);
assertEquals(input.length(), result.length());
}
// =========================================================================
// 5. 온라인 정보 (IP는 고정 길이 마스킹 — String.length() 보존 단언 없음)
// =========================================================================
// --- 5-1. IPv4 ---
@Test
@DisplayName("5-1-1. IPv4 — 3번째 옥텟 고정 마스킹 (***)")
void testIpv4_normal() {
assertEquals("100.200.***.45", MaskingUtils.maskIpv4("100.200.123.45"));
}
@Test
@DisplayName("5-1-2. IPv4 — 3rd octet 2자리: *** 고정으로 길이 증가 (의도된 동작)")
void testIpv4_twoDigitOctet() {
String input = "10.0.10.5";
String result = MaskingUtils.maskIpv4(input);
assertEquals("10.0.***.5", result);
// 2자리(10) → 3자리(***): length 9 → 10
assertEquals(input.length() + 1, result.length());
}
@Test
@DisplayName("5-1-3. IPv4 — 3rd octet 1자리: *** 고정으로 길이 증가 (의도된 동작)")
void testIpv4_oneDigitOctet() {
String input = "10.0.1.5";
String result = MaskingUtils.maskIpv4(input);
assertEquals("10.0.***.5", result);
// 1자리(1) → 3자리(***): length 8 → 10
assertEquals(input.length() + 2, result.length());
}
@Test
@DisplayName("5-1-4. IPv4 — 형식이 아닌 경우 원본 반환")
void testIpv4_invalidFormat() {
assertEquals("100.200.123", MaskingUtils.maskIpv4("100.200.123"));
}
@Test
@DisplayName("5-1-5. IPv4 — null/빈 문자열 방어")
void testIpv4_nullAndEmpty() {
assertNull(MaskingUtils.maskIpv4(null));
assertEquals("", MaskingUtils.maskIpv4(""));
}
// --- 5-2. IPv6 ---
@Test
@DisplayName("5-2-1. IPv6 — 마지막 그룹(113~128 비트) 고정 마스킹 (****)")
void testIpv6_normal() {
assertEquals(
"21DA:00D3:0000:2F3B:02AA:00FF:FE28:****",
MaskingUtils.maskIpv6("21DA:00D3:0000:2F3B:02AA:00FF:FE28:9C5A")
);
}
@Test
@DisplayName("5-2-2. IPv6 — null/빈 문자열 방어")
void testIpv6_nullAndEmpty() {
assertNull(MaskingUtils.maskIpv6(null));
assertEquals("", MaskingUtils.maskIpv6(""));
}
// --- 5-3. IP 자동 판별 ---
@Test
@DisplayName("5-3-1. IP 자동 판별 — IPv4")
void testIpAddress_detectIpv4() {
assertEquals("192.168.***.1", MaskingUtils.maskIpAddress("192.168.100.1"));
}
@Test
@DisplayName("5-3-2. IP 자동 판별 — IPv6")
void testIpAddress_detectIpv6() {
assertEquals(
"21DA:00D3:0000:2F3B:02AA:00FF:FE28:****",
MaskingUtils.maskIpAddress("21DA:00D3:0000:2F3B:02AA:00FF:FE28:9C5A")
);
}
@Test
@DisplayName("5-3-3. IP 자동 판별 — 쉼표 구분 다중 IPv4 (3자리/1자리 혼합)")
void testIpAddress_multipleIpv4() {
// 192.168.100.1: 3rd octet 3자리 → 길이 유지
// 10.0.0.2: 3rd octet 1자리(0) → *** 고정으로 길이 증가
assertEquals("192.168.***.1,10.0.***.2", MaskingUtils.maskIpAddress("192.168.100.1,10.0.0.2"));
}
}