""" Database 단위 테스트 — 마이그레이션, 동기화, 헬퍼. """ import os import sys import tempfile from datetime import date import pytest sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from core.database import Database @pytest.fixture def fresh_db(tmp_path): """매 테스트마다 빈 DB.""" return Database(str(tmp_path / "test.db")) class TestSettingsHelpers: def test_get_setting_int_valid(self, fresh_db): fresh_db.set_setting('foo', '42') assert fresh_db.get_setting_int('foo') == 42 def test_get_setting_int_invalid_returns_default(self, fresh_db): fresh_db.set_setting('foo', 'abc') assert fresh_db.get_setting_int('foo', 99) == 99 def test_get_setting_int_missing_returns_default(self, fresh_db): assert fresh_db.get_setting_int('missing', 7) == 7 def test_get_setting_bool_truthy(self, fresh_db): fresh_db.set_setting('flag', 'true') assert fresh_db.get_setting_bool('flag') is True def test_get_setting_bool_falsy(self, fresh_db): fresh_db.set_setting('flag', 'no') assert fresh_db.get_setting_bool('flag') is False def test_get_setting_float(self, fresh_db): fresh_db.set_setting('rate', '3.14') assert fresh_db.get_setting_float('rate') == 3.14 class TestSettingsAutoSync: def test_work_minutes_to_work_hours_floor(self, fresh_db): """work_minutes 저장 시 work_hours는 floor 동기화 (450 → 7)""" fresh_db.save_settings({'work_minutes': 450}) assert fresh_db.get_setting('work_hours') == '7' def test_work_hours_to_work_minutes(self, fresh_db): fresh_db.save_settings({'work_hours': 8}) assert fresh_db.get_setting('work_minutes') == '480' def test_annual_leave_bidirectional(self, fresh_db): fresh_db.save_settings({'annual_leave_days': 12}) assert fresh_db.get_setting('annual_leave_total') == '12' class TestWorkMinutes: def test_get_work_minutes_default(self, fresh_db): assert fresh_db.get_work_minutes() == 480 def test_get_work_minutes_after_save(self, fresh_db): fresh_db.save_settings({'work_minutes': 450}) assert fresh_db.get_work_minutes() == 450 class TestLeaveCalculation: def test_leave_minutes_for_short_worker(self, fresh_db): """단축근무자(7h30m) 1일 연차 = 450분""" fresh_db.save_settings({'work_minutes': 450}) today = date.today().isoformat() fresh_db.add_leave_record(today, 'annual', 1.0) assert fresh_db.get_today_leave_minutes() == 450 def test_half_day_leave(self, fresh_db): today = date.today().isoformat() fresh_db.add_leave_record(today, 'half', 0.5) assert fresh_db.get_today_leave_minutes() == 240 # 8h * 0.5 class TestMigrationIdempotency: def test_annual_leave_keys_migrated_sentinel(self, fresh_db): assert fresh_db.get_setting('annual_leave_keys_migrated') == 'true' def test_re_init_does_not_break(self, tmp_path): path = str(tmp_path / "test.db") db1 = Database(path) db1.save_settings({'work_minutes': 450}) # 두 번째 init db2 = Database(path) assert db2.get_work_minutes() == 450 class TestConsecutiveOvertimeDays: def test_no_records(self, fresh_db): assert fresh_db.get_consecutive_overtime_days() == 0 def test_three_consecutive(self, fresh_db): from datetime import date, timedelta today = date.today() for i in range(3): d = (today - timedelta(days=i)).isoformat() fresh_db.add_work_record(d, '09:00:00') fresh_db.update_clock_out(d, '20:00:00', total_hours=11.0, overtime_minutes=120, overtime_earned=120) assert fresh_db.get_consecutive_overtime_days() == 3 class TestLeaveQueriesByDate: def test_get_leave_minutes_for_no_records(self, fresh_db): assert fresh_db.get_leave_minutes_for('2026-05-01') == 0 def test_full_day_leave_detected(self, fresh_db): fresh_db.add_leave_record('2026-05-15', '연차', 1.0, '여행') assert fresh_db.has_full_day_leave('2026-05-15') assert fresh_db.get_leave_minutes_for('2026-05-15') == 480 def test_half_day_not_full(self, fresh_db): fresh_db.add_leave_record('2026-05-15', '반차', 0.5) assert not fresh_db.has_full_day_leave('2026-05-15') assert fresh_db.get_leave_minutes_for('2026-05-15') == 240 def test_two_halves_become_full(self, fresh_db): fresh_db.add_leave_record('2026-05-15', '오전반차', 0.5) fresh_db.add_leave_record('2026-05-15', '오후반차', 0.5) assert fresh_db.has_full_day_leave('2026-05-15') assert fresh_db.get_leave_minutes_for('2026-05-15') == 480 def test_records_by_date(self, fresh_db): fresh_db.add_leave_record('2026-05-15', '연차', 1.0, '메모') recs = fresh_db.get_leave_records_by_date('2026-05-15') assert len(recs) == 1 assert recs[0]['leave_type'] == '연차' assert recs[0]['memo'] == '메모' def test_records_by_range(self, fresh_db): fresh_db.add_leave_record('2026-05-01', '연차', 1.0) fresh_db.add_leave_record('2026-05-10', '반차', 0.5) fresh_db.add_leave_record('2026-06-01', '연차', 1.0) recs = fresh_db.get_leave_records_by_range('2026-05-01', '2026-05-31') assert len(recs) == 2 # 날짜 정렬 assert recs[0]['date'] == '2026-05-01' assert recs[1]['date'] == '2026-05-10' class TestRecurringLeavesDB: def test_add_and_list(self, fresh_db): rid = fresh_db.add_recurring_leave( 'weekly:friday', '반차', 0.5, '2026-05-01', '2026-12-31', '단축' ) assert rid > 0 recs = fresh_db.get_recurring_leaves() assert len(recs) == 1 assert recs[0]['pattern'] == 'weekly:friday' assert recs[0]['memo'] == '단축' def test_active_on_filter(self, fresh_db): # 종료일이 지난 패턴 fresh_db.add_recurring_leave('weekly:fri', '반차', 0.5, '2025-01-01', '2025-12-31') # 아직 시작 안 한 패턴 fresh_db.add_recurring_leave('weekly:mon', '반차', 0.5, '2027-01-01', None) # 현재 활성 패턴 fresh_db.add_recurring_leave('monthly:15', '연차', 1.0, '2026-01-01', None) active = fresh_db.get_recurring_leaves(active_on='2026-05-15') assert len(active) == 1 assert active[0]['pattern'] == 'monthly:15' def test_delete(self, fresh_db): rid = fresh_db.add_recurring_leave('weekly:fri', '반차', 0.5, '2026-01-01') assert len(fresh_db.get_recurring_leaves()) == 1 fresh_db.delete_recurring_leave(rid) assert fresh_db.get_recurring_leaves() == [] def test_recurring_contributes_to_leave_minutes(self, fresh_db): # 매주 금요일 반차 fresh_db.add_recurring_leave('weekly:friday', '반차', 0.5, '2026-01-01') # 2026-05-01 = Friday → 240분 assert fresh_db.get_leave_minutes_for('2026-05-01') == 240 # 2026-05-04 = Monday → 0분 assert fresh_db.get_leave_minutes_for('2026-05-04') == 0 def test_concrete_plus_recurring_sum(self, fresh_db): # 매주 금요일 반차 + 그날 별도 반반차 추가 fresh_db.add_recurring_leave('weekly:friday', '반차', 0.5, '2026-01-01') fresh_db.add_leave_record('2026-05-01', '반반차', 0.25) # 0.5 + 0.25 = 0.75일 = 360분 assert fresh_db.get_leave_minutes_for('2026-05-01') == 360 assert not fresh_db.has_full_day_leave('2026-05-01') def test_concrete_plus_recurring_full_day(self, fresh_db): fresh_db.add_recurring_leave('weekly:friday', '반차', 0.5, '2026-01-01') fresh_db.add_leave_record('2026-05-01', '오후반차', 0.5) # 0.5 + 0.5 = 1.0일 assert fresh_db.has_full_day_leave('2026-05-01')