MIL-003: Step 2 - New flash cards

Review record RC-025 (Go, autonomous mode, not independent).

Task: MIL-003#1
Task: MIL-003#2
Task: MIL-003#3
Task: MIL-003#4
Task: MIL-003#5
This commit is contained in:
2026-10-14 21:59:29 +08:00
parent c6c737ce40
commit 8d66c43eea
36 changed files with 1167 additions and 102 deletions
@@ -0,0 +1,95 @@
"""Tests of read_word_list and CsvVocabularyRepository."""
from pathlib import Path
import pytest
from flash_card.domain.entities.card import Card
from flash_card.domain.errors.vocabulary_errors import (
VocabularyFormatError,
VocabularyNotFoundError,
)
from flash_card.domain.value_objects.language_pair import LanguagePair
from flash_card.infrastructure.persistence.csv_vocabulary_repository import (
CsvVocabularyRepository,
)
from flash_card.infrastructure.persistence.csv_word_list import read_word_list
def _write(path: Path, content: str) -> Path:
path.write_bytes(content.encode("utf-8"))
return path
def test_read_word_list_returns_pair_and_cards(tmp_path: Path) -> None:
path = _write(tmp_path / "w.csv", "ms;da\naku;mig\nkau;du\n")
pair, cards = read_word_list(path)
assert pair == LanguagePair("ms", "da")
assert cards == (Card("aku", "mig"), Card("kau", "du"))
def test_read_word_list_drops_byte_order_mark(tmp_path: Path) -> None:
path = _write(tmp_path / "w.csv", "ms;da\r\naku;mig\r\n")
pair, cards = read_word_list(path)
assert pair == LanguagePair("ms", "da")
assert cards == (Card("aku", "mig"),)
def test_read_word_list_skips_blank_lines(tmp_path: Path) -> None:
path = _write(tmp_path / "w.csv", "\nms;da\n\naku;mig\n \n;\nkau;du\n\n")
_pair, cards = read_word_list(path)
assert cards == (Card("aku", "mig"), Card("kau", "du"))
def test_read_word_list_strips_whitespace(tmp_path: Path) -> None:
path = _write(tmp_path / "w.csv", " ms ; da \n aku ; mig \n")
pair, cards = read_word_list(path)
assert pair == LanguagePair("ms", "da")
assert cards == (Card("aku", "mig"),)
def test_read_word_list_raises_not_found_when_file_is_missing(
tmp_path: Path,
) -> None:
with pytest.raises(VocabularyNotFoundError) as info:
read_word_list(tmp_path / "missing.csv")
assert isinstance(info.value.__cause__, FileNotFoundError)
def test_read_word_list_raises_format_error_when_file_is_empty(
tmp_path: Path,
) -> None:
path = _write(tmp_path / "w.csv", "\n\n")
with pytest.raises(VocabularyFormatError, match="empty"):
read_word_list(path)
@pytest.mark.parametrize("header", ["ms\n", "ms;da;en\n", "ms;\n"])
def test_read_word_list_raises_format_error_for_bad_header(
tmp_path: Path, header: str
) -> None:
path = _write(tmp_path / "w.csv", header + "aku;mig\n")
with pytest.raises(VocabularyFormatError, match="line 1"):
read_word_list(path)
def test_read_word_list_raises_format_error_with_line_number_for_bad_row(
tmp_path: Path,
) -> None:
path = _write(tmp_path / "w.csv", "ms;da\naku;mig\n\nkau;du;extra\n")
with pytest.raises(VocabularyFormatError, match="line 4") as info:
read_word_list(path)
assert str(path) in str(info.value)
def test_repository_load_returns_vocabulary(tmp_path: Path) -> None:
path = _write(tmp_path / "w.csv", "ms;da\naku;mig\n")
vocabulary = CsvVocabularyRepository(path).load()
assert vocabulary.language_pair == LanguagePair("ms", "da")
assert vocabulary.cards == (Card("aku", "mig"),)
def test_repository_load_raises_format_error_when_no_card(tmp_path: Path) -> None:
path = _write(tmp_path / "w.csv", "ms;da\n")
with pytest.raises(VocabularyFormatError):
CsvVocabularyRepository(path).load()