""" core.recurring_leaves 단위 테스트. """ import os import sys from datetime import date import pytest sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from core.recurring_leaves import ( matches, expand_for_range, expand_for_date, describe_pattern, _parse_pattern, ) class TestParsePattern: @pytest.mark.parametrize("pattern,expected_kind", [ ('weekly:friday', 'weekly'), ('weekly:fri', 'weekly'), ('weekly:mon,wed,fri', 'weekly'), ('biweekly:friday', 'biweekly'), ('monthly:15', 'monthly'), ('monthly:1', 'monthly'), ]) def test_valid(self, pattern, expected_kind): result = _parse_pattern(pattern) assert result is not None assert result[0] == expected_kind @pytest.mark.parametrize("pattern", [ '', 'weekly', 'weekly:', 'weekly:xyz', 'monthly:0', 'monthly:32', 'monthly:abc', 'unknown:fri', None, ]) def test_invalid(self, pattern): assert _parse_pattern(pattern) is None class TestMatches: def _rec(self, pattern, start='2026-01-01', end=None, days=0.5, leave_type='반차'): return { 'pattern': pattern, 'start_date': start, 'end_date': end, 'days': days, 'leave_type': leave_type, } def test_weekly_single_day(self): rec = self._rec('weekly:friday') assert matches(rec, date(2026, 5, 1)) # Fri assert not matches(rec, date(2026, 5, 2)) # Sat assert not matches(rec, date(2026, 5, 4)) # Mon def test_weekly_multiple_days(self): rec = self._rec('weekly:mon,wed,fri') assert matches(rec, date(2026, 5, 4)) # Mon assert matches(rec, date(2026, 5, 6)) # Wed assert matches(rec, date(2026, 5, 8)) # Fri assert not matches(rec, date(2026, 5, 5)) # Tue assert not matches(rec, date(2026, 5, 7)) # Thu def test_biweekly_alignment(self): # start_date 2026-01-02 = Friday (week 0) rec = self._rec('biweekly:friday', start='2026-01-02') assert matches(rec, date(2026, 1, 2)) # week 0 assert not matches(rec, date(2026, 1, 9)) # week 1 assert matches(rec, date(2026, 1, 16)) # week 2 def test_monthly(self): rec = self._rec('monthly:15') assert matches(rec, date(2026, 1, 15)) assert matches(rec, date(2026, 5, 15)) assert not matches(rec, date(2026, 5, 14)) assert not matches(rec, date(2026, 5, 16)) def test_monthly_skipped_in_short_month(self): # 31일은 30일 달에는 매치되지 않음 rec = self._rec('monthly:31') assert matches(rec, date(2026, 1, 31)) assert not matches(rec, date(2026, 4, 30)) # 4월 31일 없음 def test_before_start(self): rec = self._rec('weekly:friday', start='2026-05-01') assert matches(rec, date(2026, 5, 1)) assert not matches(rec, date(2026, 4, 24)) # 시작 전 def test_after_end(self): rec = self._rec('weekly:friday', start='2026-01-01', end='2026-04-30') assert matches(rec, date(2026, 4, 24)) # 종료일 이전 금요일 assert not matches(rec, date(2026, 5, 1)) # 종료일 이후 def test_no_end_means_forever(self): rec = self._rec('weekly:friday', start='2026-01-01', end=None) assert matches(rec, date(2030, 1, 4)) # 4년 후 금요일 def test_invalid_pattern_returns_false(self): rec = self._rec('garbage:xyz') assert not matches(rec, date(2026, 5, 1)) class TestExpandRange: def _rec(self, pattern, start='2026-01-01'): return { 'id': 1, 'pattern': pattern, 'start_date': start, 'end_date': None, 'days': 0.5, 'leave_type': '반차', 'memo': '', } def test_expand_weekly_one_month(self): rec = self._rec('weekly:friday') occs = expand_for_range([rec], date(2026, 5, 1), date(2026, 5, 31)) # 5월 금요일: 1, 8, 15, 22, 29 = 5회 assert len(occs) == 5 assert all(o.date.weekday() == 4 for o in occs) def test_expand_empty_when_outside(self): rec = self._rec('weekly:friday', start='2027-01-01') occs = expand_for_range([rec], date(2026, 5, 1), date(2026, 5, 31)) assert occs == [] def test_expand_invalid_range(self): # start > end rec = self._rec('weekly:friday') occs = expand_for_range([rec], date(2026, 5, 31), date(2026, 5, 1)) assert occs == [] def test_expand_multiple_recs(self): rec_fri = self._rec('weekly:friday') rec_mon = self._rec('weekly:monday') rec_mon['id'] = 2 occs = expand_for_range([rec_fri, rec_mon], date(2026, 5, 1), date(2026, 5, 7)) # 5/1=Fri (rec_fri), 5/4=Mon (rec_mon) assert len(occs) == 2 def test_expand_for_date_single(self): rec = self._rec('monthly:15') occs = expand_for_date([rec], date(2026, 5, 15)) assert len(occs) == 1 assert occs[0].date == date(2026, 5, 15) class TestDescribePattern: def test_weekly_korean(self): assert '매주' in describe_pattern('weekly:friday') assert '금' in describe_pattern('weekly:friday') def test_biweekly(self): assert '격주' in describe_pattern('biweekly:friday') def test_monthly(self): assert '매월' in describe_pattern('monthly:15') assert '15' in describe_pattern('monthly:15')