Clock_out_Time_Calculator/tests/test_csv_importer.py
KINDNICK ff71886fd7 v2.7.0: i18n 100% + 런타임 retranslate + 테스트 +47 + 폴리싱
- i18n 사전 100% (break/overtime/leave/clockin) — 50+ 신규 키
- 런타임 재번역 인프라 (ui/i18n_runtime.py) — 재시작 없이 메인 UI 적용
- MealController 분리 — 점심/저녁 토글을 컨트롤러로 추출
- 통합 테스트 +15 (S36-S52: 온보딩/salary/CSV/notification dedupe 등)
- pytest 신규 4종 + i18n_runtime 테스트 (총 122 케이스, 90→122)
- README/INSTALL/CLAUDE/AGENTS v2.6+ 아키텍처 반영
2026-04-30 19:30:47 +09:00

128 lines
3.9 KiB
Python

"""
utils.csv_importer 단위 테스트.
"""
import os
import sys
import tempfile
from pathlib import Path
import pytest
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from utils.csv_importer import parse_csv, _normalize_row, _normalize_time
class TestNormalizeTime:
def test_hh_mm_to_hh_mm_ss(self):
assert _normalize_time('09:00', 'clock_in') == '09:00:00'
def test_hh_mm_ss_unchanged(self):
assert _normalize_time('09:00:00', 'clock_in') == '09:00:00'
def test_empty_raises(self):
with pytest.raises(ValueError):
_normalize_time('', 'clock_in')
def test_invalid_format_raises(self):
with pytest.raises(ValueError):
_normalize_time('foo', 'clock_in')
with pytest.raises(ValueError):
_normalize_time('25:00', 'clock_in')
class TestNormalizeRow:
def test_basic_row(self):
row = {
'date': '2026-04-01',
'clock_in': '09:00',
'clock_out': '18:00',
'lunch_minutes': '60',
'memo': '메모',
}
out = _normalize_row(row)
assert out['date'] == '2026-04-01'
assert out['clock_in'] == '09:00:00'
assert out['clock_out'] == '18:00:00'
assert out['lunch_minutes'] == 60
assert out['memo'] == '메모'
def test_optional_clock_out(self):
row = {'date': '2026-04-01', 'clock_in': '09:00', 'clock_out': '',
'lunch_minutes': '0', 'memo': ''}
out = _normalize_row(row)
assert out['clock_out'] is None
def test_invalid_date(self):
row = {'date': 'not-a-date', 'clock_in': '09:00', 'clock_out': '',
'lunch_minutes': '0', 'memo': ''}
with pytest.raises(ValueError):
_normalize_row(row)
def test_negative_lunch_minutes(self):
row = {'date': '2026-04-01', 'clock_in': '09:00', 'clock_out': '',
'lunch_minutes': '-30', 'memo': ''}
with pytest.raises(ValueError):
_normalize_row(row)
class TestParseCsv:
def _write(self, content: str) -> str:
f = tempfile.NamedTemporaryFile('w', encoding='utf-8',
delete=False, suffix='.csv', newline='')
f.write(content)
f.close()
return f.name
def test_valid_csv(self):
path = self._write(
"date,clock_in,clock_out,lunch_minutes,memo\n"
"2026-04-01,09:00,18:00,60,첫째날\n"
"2026-04-02,09:30:00,17:30:00,30,단축\n"
)
try:
rows = parse_csv(path)
assert len(rows) == 2
assert rows[0]['lunch_minutes'] == 60
assert rows[1]['memo'] == '단축'
finally:
os.remove(path)
def test_utf8_bom(self):
# 엑셀 저장본 호환
path = self._write('\ufeff' +
"date,clock_in,clock_out,lunch_minutes,memo\n"
"2026-04-01,09:00,18:00,60,첫째날\n"
)
try:
rows = parse_csv(path)
assert len(rows) == 1
finally:
os.remove(path)
def test_missing_required_header(self):
path = self._write("date,memo\n2026-04-01,foo\n")
try:
with pytest.raises(ValueError) as exc:
parse_csv(path)
assert 'clock_in' in str(exc.value)
finally:
os.remove(path)
def test_file_not_found(self):
with pytest.raises(FileNotFoundError):
parse_csv('/nonexistent/file.csv')
def test_line_number_in_error(self):
path = self._write(
"date,clock_in,clock_out,lunch_minutes,memo\n"
"2026-04-01,09:00,18:00,60,ok\n"
"bad-date,09:00,18:00,60,broken\n"
)
try:
with pytest.raises(ValueError) as exc:
parse_csv(path)
assert '줄 3' in str(exc.value)
finally:
os.remove(path)