- 新增 _calculate_lane_conflicts_v2() 分開返回標籤重疊和線穿框分數 - 修改泳道選擇算法,優先選擇無標籤重疊的泳道 - 兩階段搜尋:優先側別無可用泳道則嘗試另一側 - 增強日誌輸出,顯示標籤範圍和詳細衝突分數 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
246 lines
7.3 KiB
Python
246 lines
7.3 KiB
Python
"""
|
|
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"])
|