testsAndMisc/python_pkg/word_frequency/tests/test_generation_part2.py
Krzysztof kuhy Rudnicki 2545d72710 test: achieve 100% branch coverage across all python_pkg packages
- Add comprehensive tests for all packages (3572 tests, 100% branch coverage)
- Split oversized test files to stay under 500-line limit
- Add per-file ruff ignores for test-appropriate suppressions
- Fix _cache_decks.py to properly convert JSON lists to tuples
- Add session-scoped conftest fixture for logging handler cleanup (Python 3.14)
- Update ruff pre-commit hook to v0.15.2
- Add codespell ignore words for test data
- Add generated output files to .gitignore
2026-03-21 17:51:36 +01:00

366 lines
12 KiB
Python

"""Tests for _generation.generate_flashcards_inverse (lines 323-379)."""
from __future__ import annotations
from typing import TYPE_CHECKING
from unittest.mock import patch
import pytest
from python_pkg.word_frequency._generation import generate_flashcards_inverse
from python_pkg.word_frequency._types import FlashcardOptions
if TYPE_CHECKING:
from pathlib import Path
_GEN = "python_pkg.word_frequency._generation"
class TestGenerateFlashcardsInverse:
"""Tests for generate_flashcards_inverse."""
def test_basic_flow(self, tmp_path: Path) -> None:
"""Cover the happy path through all branches."""
fp = tmp_path / "t.txt"
fp.write_text("hello world", encoding="utf-8")
inverse_output = (
"INVERSE_MODE\n"
"Longest excerpt: 5 words\n"
'Excerpt: "hello world foo bar baz"\n'
"Max rank used: 3\n"
"\nVOCAB_DUMP_START\nhello;1\nworld;2\nfoo;3\nVOCAB_DUMP_END\n"
)
with (
patch(
f"{_GEN}.run_vocabulary_curve_inverse",
return_value=inverse_output,
),
patch(
f"{_GEN}.parse_inverse_mode_output",
return_value=(
"hello world foo bar baz",
5,
3,
[("hello", 1), ("world", 2), ("foo", 3)],
),
),
patch(
f"{_GEN}.detect_language",
return_value="en",
),
patch(
f"{_GEN}.generate_anki_deck",
return_value="deck content",
),
):
content, excerpt, length, n_words, max_rank = generate_flashcards_inverse(
fp,
3,
FlashcardOptions(source_lang="en"),
)
assert content == "deck content"
assert excerpt == "hello world foo bar baz"
assert length == 5
assert n_words == 3
assert max_rank == 3
def test_default_options(self, tmp_path: Path) -> None:
"""Cover options=None branch (line 323)."""
fp = tmp_path / "t.txt"
fp.write_text("hello world", encoding="utf-8")
with (
patch(
f"{_GEN}.run_vocabulary_curve_inverse",
return_value="out",
),
patch(
f"{_GEN}.parse_inverse_mode_output",
return_value=(
"hello world",
2,
2,
[("hello", 1), ("world", 2)],
),
),
patch(
f"{_GEN}.detect_language",
return_value="en",
),
patch(
f"{_GEN}.generate_anki_deck",
return_value="deck",
),
):
result = generate_flashcards_inverse(fp, 2)
assert result[0] == "deck"
def test_excerpt_length_zero_raises(self, tmp_path: Path) -> None:
"""Cover the excerpt_length == 0 ValueError branch."""
fp = tmp_path / "t.txt"
fp.write_text("text", encoding="utf-8")
with (
patch(
f"{_GEN}.run_vocabulary_curve_inverse",
return_value="out",
),
patch(
f"{_GEN}.parse_inverse_mode_output",
return_value=("", 0, 0, []),
),
pytest.raises(ValueError, match="No valid excerpt found"),
):
generate_flashcards_inverse(fp, 5, FlashcardOptions(source_lang="en"))
def test_no_vocab_words_raises(self, tmp_path: Path) -> None:
"""Cover the 'not all_vocab_words' ValueError branch."""
fp = tmp_path / "t.txt"
fp.write_text("text", encoding="utf-8")
with (
patch(
f"{_GEN}.run_vocabulary_curve_inverse",
return_value="out",
),
patch(
f"{_GEN}.parse_inverse_mode_output",
return_value=("hello", 1, 1, []),
),
pytest.raises(ValueError, match="No vocabulary returned"),
):
generate_flashcards_inverse(fp, 5, FlashcardOptions(source_lang="en"))
def test_include_context(self, tmp_path: Path) -> None:
"""Cover include_context=True path (context generation)."""
fp = tmp_path / "t.txt"
fp.write_text("hello world foo", encoding="utf-8")
with (
patch(
f"{_GEN}.run_vocabulary_curve_inverse",
return_value="out",
),
patch(
f"{_GEN}.parse_inverse_mode_output",
return_value=(
"hello world",
2,
2,
[("hello", 1), ("world", 2)],
),
),
patch(
f"{_GEN}.detect_language",
return_value="en",
),
patch(
f"{_GEN}.find_word_contexts",
return_value={"hello": "...hello..."},
) as mock_ctx,
patch(
f"{_GEN}.generate_anki_deck",
return_value="deck",
),
):
generate_flashcards_inverse(
fp,
2,
FlashcardOptions(
source_lang="en",
include_context=True,
),
)
mock_ctx.assert_called_once()
def test_include_context_rereads_when_empty(self, tmp_path: Path) -> None:
"""Cover the 'if not text' re-read branch inside context."""
fp = tmp_path / "t.txt"
fp.write_text("", encoding="utf-8")
with (
patch(
f"{_GEN}.run_vocabulary_curve_inverse",
return_value="out",
),
patch(
f"{_GEN}.parse_inverse_mode_output",
return_value=(
"hello",
1,
1,
[("hello", 1)],
),
),
patch(
f"{_GEN}.generate_anki_deck",
return_value="deck",
),
patch(f"{_GEN}.find_word_contexts", return_value={}),
patch(f"{_GEN}.read_file", return_value="") as mock_read,
):
generate_flashcards_inverse(
fp,
1,
FlashcardOptions(
source_lang="en",
include_context=True,
),
)
# read_file called twice: once for initial text, once for context
assert mock_read.call_count == 2
def test_auto_detect_language(self, tmp_path: Path) -> None:
"""Cover source_lang=None auto-detection path."""
fp = tmp_path / "t.txt"
fp.write_text("hola mundo", encoding="utf-8")
with (
patch(
f"{_GEN}.run_vocabulary_curve_inverse",
return_value="out",
),
patch(
f"{_GEN}.parse_inverse_mode_output",
return_value=(
"hola mundo",
2,
2,
[("hola", 1), ("mundo", 2)],
),
),
patch(
f"{_GEN}.detect_language",
return_value="es",
) as mock_detect,
patch(
f"{_GEN}.generate_anki_deck",
return_value="deck",
),
):
generate_flashcards_inverse(fp, 2, FlashcardOptions(source_lang=None))
mock_detect.assert_called_once()
def test_custom_deck_name(self, tmp_path: Path) -> None:
"""Cover deck_name from options."""
fp = tmp_path / "t.txt"
fp.write_text("hello", encoding="utf-8")
with (
patch(
f"{_GEN}.run_vocabulary_curve_inverse",
return_value="out",
),
patch(
f"{_GEN}.parse_inverse_mode_output",
return_value=(
"hello",
1,
1,
[("hello", 1)],
),
),
patch(
f"{_GEN}.generate_anki_deck",
return_value="deck",
) as mock_deck,
):
generate_flashcards_inverse(
fp,
1,
FlashcardOptions(
source_lang="en",
deck_name="MyDeck",
),
)
call_kwargs = mock_deck.call_args
deck_input = call_kwargs[0][0]
assert deck_input.deck_name == "MyDeck"
def test_default_deck_name(self, tmp_path: Path) -> None:
"""Cover auto-generated deck_name when none provided."""
fp = tmp_path / "sample.txt"
fp.write_text("hello", encoding="utf-8")
with (
patch(
f"{_GEN}.run_vocabulary_curve_inverse",
return_value="out",
),
patch(
f"{_GEN}.parse_inverse_mode_output",
return_value=(
"hello",
1,
1,
[("hello", 1)],
),
),
patch(
f"{_GEN}.generate_anki_deck",
return_value="deck",
) as mock_deck,
):
generate_flashcards_inverse(
fp,
5,
FlashcardOptions(source_lang="en", deck_name=None),
)
deck_input = mock_deck.call_args[0][0]
assert deck_input.deck_name == "sample_top5"
def test_excerpt_words_filtering(self, tmp_path: Path) -> None:
"""Cover the excerpt_words filtering logic."""
fp = tmp_path / "t.txt"
fp.write_text("hello world", encoding="utf-8")
with (
patch(
f"{_GEN}.run_vocabulary_curve_inverse",
return_value="out",
),
patch(
f"{_GEN}.parse_inverse_mode_output",
return_value=(
"hello",
1,
2,
[("hello", 1), ("world", 2), ("foo", 3)],
),
),
patch(
f"{_GEN}.generate_anki_deck",
return_value="deck",
) as mock_deck,
):
generate_flashcards_inverse(fp, 3, FlashcardOptions(source_lang="en"))
call_kwargs = mock_deck.call_args
excerpt_words = call_kwargs[1]["excerpt_words"]
# Only "hello" is in the excerpt, not "world" or "foo"
assert len(excerpt_words) == 1
assert excerpt_words[0][0] == "hello"
def test_no_translate(self, tmp_path: Path) -> None:
"""Cover no_translate option."""
fp = tmp_path / "t.txt"
fp.write_text("text", encoding="utf-8")
with (
patch(
f"{_GEN}.run_vocabulary_curve_inverse",
return_value="out",
),
patch(
f"{_GEN}.parse_inverse_mode_output",
return_value=(
"hello",
1,
1,
[("hello", 1)],
),
),
patch(
f"{_GEN}.generate_anki_deck",
return_value="deck",
) as mock_deck,
):
generate_flashcards_inverse(
fp,
1,
FlashcardOptions(
source_lang="en",
no_translate=True,
),
)
assert mock_deck.call_args[1]["no_translate"] is True