""" CSV/XLSX 匯入模組單元測試 對應 TDD.md - UT-IMP-01: 匯入 CSV 欄位解析 驗證重點: - 欄位自動對應 - 格式容錯 - 錯誤處理 Version: 1.0.0 DocID: TDD-UT-IMP-001 Related: SDD-API-001 (POST /import) """ import pytest import os from pathlib import Path from datetime import datetime from backend.schemas import Event, ImportResult, EventType from backend.importer import CSVImporter, FieldMapper, DateParser, ColorValidator # 測試資料路徑 FIXTURES_DIR = Path(__file__).parent.parent / "fixtures" SAMPLE_CSV = FIXTURES_DIR / "sample_events.csv" INVALID_CSV = FIXTURES_DIR / "invalid_dates.csv" class TestFieldMapper: """欄位映射器測試""" def test_map_english_fields(self): """測試英文欄位映射""" headers = ['id', 'title', 'start', 'end', 'group', 'description', 'color'] mapping = FieldMapper.map_fields(headers) assert mapping['id'] == 'id' assert mapping['title'] == 'title' assert mapping['start'] == 'start' assert mapping['end'] == 'end' def test_map_chinese_fields(self): """測試中文欄位映射""" headers = ['編號', '標題', '開始', '結束', '群組'] mapping = FieldMapper.map_fields(headers) assert mapping['id'] == '編號' assert mapping['title'] == '標題' assert mapping['start'] == '開始' def test_validate_missing_fields(self): """測試缺少必要欄位驗證""" mapping = {'id': 'id', 'title': 'title'} # 缺少 start missing = FieldMapper.validate_required_fields(mapping) assert 'start' in missing class TestDateParser: """日期解析器測試""" def test_parse_standard_format(self): """測試標準日期格式""" result = DateParser.parse('2024-01-01 09:00:00') assert result == datetime(2024, 1, 1, 9, 0, 0) def test_parse_date_only(self): """測試僅日期格式""" result = DateParser.parse('2024-01-01') assert result.year == 2024 assert result.month == 1 assert result.day == 1 def test_parse_slash_format(self): """測試斜線格式""" result = DateParser.parse('2024/01/01') assert result.year == 2024 def test_parse_invalid_date(self): """測試無效日期""" result = DateParser.parse('invalid-date') assert result is None def test_parse_empty_string(self): """測試空字串""" result = DateParser.parse('') assert result is None class TestColorValidator: """顏色驗證器測試""" def test_validate_valid_hex(self): """測試有效的 HEX 顏色""" result = ColorValidator.validate('#3B82F6') assert result == '#3B82F6' def test_validate_hex_without_hash(self): """測試不含 # 的 HEX 顏色""" result = ColorValidator.validate('3B82F6') assert result == '#3B82F6' def test_validate_invalid_color(self): """測試無效顏色,應返回預設顏色""" result = ColorValidator.validate('invalid') assert result.startswith('#') assert len(result) == 7 def test_validate_empty_color(self): """測試空顏色,應返回預設顏色""" result = ColorValidator.validate('', 0) assert result == ColorValidator.DEFAULT_COLORS[0] class TestCSVImporter: """CSV 匯入器測試類別""" def test_import_valid_csv(self): """ UT-IMP-01-001: 測試匯入有效的 CSV 檔案 預期結果: - 成功解析所有行 - 欄位正確對應 - 日期格式正確轉換 """ importer = CSVImporter() result = importer.import_file(str(SAMPLE_CSV)) assert result.success is True assert result.imported_count == 6 assert len(result.events) == 6 assert result.events[0].title == "專案啟動" assert isinstance(result.events[0].start, datetime) def test_import_with_invalid_dates(self): """ UT-IMP-01-003: 測試日期格式錯誤的 CSV 預期結果: - 部分成功匯入 - 錯誤行記錄在 errors 列表中 """ importer = CSVImporter() result = importer.import_file(str(INVALID_CSV)) assert result.success is True assert len(result.errors) > 0 # 應該有錯誤但不會完全失敗 def test_import_nonexistent_file(self): """測試匯入不存在的檔案""" importer = CSVImporter() result = importer.import_file('nonexistent.csv') assert result.success is False assert len(result.errors) > 0 assert result.imported_count == 0 def test_field_auto_mapping(self): """ UT-IMP-01-005: 測試欄位自動對應功能 測試不同的欄位名稱變體是否能正確對應 """ # 建立臨時測試 CSV test_csv = FIXTURES_DIR / "test_mapping.csv" with open(test_csv, 'w', encoding='utf-8') as f: f.write("ID,Title,Start\n") f.write("1,Test Event,2024-01-01\n") importer = CSVImporter() result = importer.import_file(str(test_csv)) assert result.success is True assert len(result.events) == 1 assert result.events[0].id == "1" assert result.events[0].title == "Test Event" # 清理 if test_csv.exists(): test_csv.unlink() def test_color_format_validation(self): """ UT-IMP-01-007: 測試顏色格式驗證 預期結果: - 有效的 HEX 顏色被接受 - 無效的顏色格式使用預設值 """ importer = CSVImporter() result = importer.import_file(str(SAMPLE_CSV)) assert result.success is True # 所有事件都應該有有效的顏色 for event in result.events: assert event.color.startswith('#') assert len(event.color) == 7 def test_import_empty_csv(self): """測試匯入空白 CSV""" # 建立空白測試 CSV empty_csv = FIXTURES_DIR / "empty.csv" with open(empty_csv, 'w', encoding='utf-8') as f: f.write("") importer = CSVImporter() result = importer.import_file(str(empty_csv)) assert result.success is False assert "空" in str(result.errors[0]) # 清理 if empty_csv.exists(): empty_csv.unlink() def test_date_format_tolerance(self): """ UT-IMP-01-006: 測試日期格式容錯 測試多種日期格式是否能正確解析 """ # 建立測試 CSV with various date formats test_csv = FIXTURES_DIR / "test_dates.csv" with open(test_csv, 'w', encoding='utf-8') as f: f.write("id,title,start\n") f.write("1,Event1,2024-01-01\n") f.write("2,Event2,2024/01/02\n") f.write("3,Event3,2024-01-03 10:00:00\n") importer = CSVImporter() result = importer.import_file(str(test_csv)) assert result.success is True assert result.imported_count == 3 assert all(isinstance(e.start, datetime) for e in result.events) # 清理 if test_csv.exists(): test_csv.unlink() if __name__ == "__main__": pytest.main([__file__, "-v"])