package com.sb.web.account.service; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.time.LocalDate; import static org.assertj.core.api.Assertions.assertThat; /** * 결제/이체 알림 파서 단위테스트. * 카드: 승인→지출 / 취소→수입(환불). 은행: 입금→수입 / 출금·이체→지출. 광고 차단. */ class CardNotificationParserTest { private final CardNotificationParser parser = new CardNotificationParser(); private final LocalDate today = LocalDate.of(2026, 6, 6); @Test @DisplayName("카드 승인 = 지출(카드)") void cardApprovalIsExpense() { var p = parser.parse("[Web발신]", "삼성카드 승인 50,000원 일시불\n스타벅스 06/06 14:30", today); assertThat(p.isRecognized()).isTrue(); assertThat(p.isIncome()).isFalse(); assertThat(p.isBankTransfer()).isFalse(); assertThat(p.getIssuer()).isEqualTo("삼성"); assertThat(p.getAmount()).isEqualTo(50000L); } @Test @DisplayName("카드 승인취소 = 수입(환불, 카드)") void cardCancelIsIncome() { var p = parser.parse("현대카드", "승인취소 12,000원", today); assertThat(p.isRecognized()).isTrue(); assertThat(p.isIncome()).isTrue(); assertThat(p.isBankTransfer()).isFalse(); assertThat(p.getIssuer()).isEqualTo("현대"); assertThat(p.getAmount()).isEqualTo(12000L); } @Test @DisplayName("은행 입금 = 수입(은행)") void bankDepositIsIncome() { var p = parser.parse("[Web발신]", "신한은행 입금 300,000원 홍길동", today); assertThat(p.isRecognized()).isTrue(); assertThat(p.isIncome()).isTrue(); assertThat(p.isBankTransfer()).isTrue(); assertThat(p.getIssuer()).isEqualTo("신한"); assertThat(p.getAmount()).isEqualTo(300000L); } @Test @DisplayName("은행 출금/이체 = 지출(은행)") void bankWithdrawIsExpense() { var p = parser.parse("국민은행", "출금 45,000원 이체", today); assertThat(p.isRecognized()).isTrue(); assertThat(p.isIncome()).isFalse(); assertThat(p.isBankTransfer()).isTrue(); assertThat(p.getAmount()).isEqualTo(45000L); } @Test @DisplayName("광고성 푸시(강신호 없음)는 무시") void blocksAdvertisement() { var p = parser.parse("카카오", "혜택 쿠폰 10,000원 적립 이벤트", today); assertThat(p.isRecognized()).isFalse(); } @Test @DisplayName("승인 신호가 있어도 명백한 광고어(이벤트/쿠폰/혜택)면 무시") void blocksAdEvenWithSignal() { var p = parser.parse("[Web발신]", "삼성카드 승인 9,900원 이벤트 쿠폰 혜택 받으세요", today); assertThat(p.isRecognized()).isFalse(); } @Test @DisplayName("실거래(승인)에 포인트/적립이 붙어도 인식 유지") void keepsRealTxnWithPoints() { var p = parser.parse("신한카드", "승인 50,000원 스타벅스 포인트 적립 120P", today); assertThat(p.isRecognized()).isTrue(); assertThat(p.isIncome()).isFalse(); assertThat(p.getAmount()).isEqualTo(50000L); } @Test @DisplayName("'결제'만 있고 약한 광고어(적립/포인트)면 광고로 무시") void blocksPaymentOnlyAd() { var p = parser.parse("[Web발신]", "5,000원 결제하고 포인트 적립 받기", today); assertThat(p.isRecognized()).isFalse(); } @Test @DisplayName("'결제'만 있어도 광고어 없으면 지출로 인식") void keepsPaymentOnlyWhenNoAd() { var p = parser.parse("롯데카드", "결제 3,000원 편의점", today); assertThat(p.isRecognized()).isTrue(); assertThat(p.isIncome()).isFalse(); assertThat(p.isBankTransfer()).isFalse(); assertThat(p.getAmount()).isEqualTo(3000L); } @Test @DisplayName("금액 없으면 무시") void requiresAmount() { assertThat(parser.parse("삼성카드", "승인 완료되었습니다", today).isRecognized()).isFalse(); } @Test @DisplayName("승인/입금/출금 등 신호 없으면 무시") void requiresSignal() { assertThat(parser.parse("삼성카드", "50,000원", today).isRecognized()).isFalse(); } @Test @DisplayName("빈 알림은 무시") void blankIsNotRecognized() { assertThat(parser.parse(null, null, today).isRecognized()).isFalse(); assertThat(parser.parse("", "", today).isRecognized()).isFalse(); } @Test @DisplayName("MM/DD 날짜를 올해 거래일로 추정") void guessesDate() { var p = parser.parse("신한카드", "승인 5,000원 6/6", today); assertThat(p.isRecognized()).isTrue(); assertThat(p.getDate()).isEqualTo(LocalDate.of(2026, 6, 6)); } }