mirror of
https://github.com/Horizontal42/Sort_YM.git
synced 2026-11-10 15:42:02 +03:00
87 lines
3.7 KiB
Python
87 lines
3.7 KiB
Python
import re
|
||
from pathlib import Path
|
||
|
||
from sort_ym import analyze
|
||
|
||
|
||
def test_theme_pattern_accepts_english_snake_case():
|
||
assert re.match(analyze.THEME_PATTERN, "digital_aging")
|
||
assert re.match(analyze.THEME_PATTERN, "modern_loneliness")
|
||
|
||
|
||
def test_theme_pattern_rejects_pilot_failure_modes():
|
||
# Пилот без паттерна давал кириллицу и составные значения через слэш вперемешку
|
||
# с английским в одном прогоне - агрегировать такое невозможно.
|
||
assert not re.match(analyze.THEME_PATTERN, "одиночество")
|
||
assert not re.match(analyze.THEME_PATTERN, "эмоциональное выгорание")
|
||
assert not re.match(analyze.THEME_PATTERN, "Digital_Aging")
|
||
assert not re.match(analyze.THEME_PATTERN, "ethereal/hopeful")
|
||
assert not re.match(analyze.THEME_PATTERN, "ab")
|
||
|
||
|
||
def test_mood_enum_excludes_stance_axis():
|
||
# Смешение осей в пилоте давало "aggressive/triumphant"; ирония/сарказм живут в stance.
|
||
assert "ironic" not in analyze.MOODS
|
||
assert "sarcastic" not in analyze.MOODS
|
||
assert "sincere" not in analyze.MOODS
|
||
assert len(analyze.MOODS) == len(set(analyze.MOODS))
|
||
|
||
|
||
def test_response_schema_constrains_mood_and_themes():
|
||
props = analyze.RESPONSE_SCHEMA["properties"]
|
||
assert props["mood"]["items"]["enum"] == analyze.MOODS
|
||
assert props["mood"]["maxItems"] == 3
|
||
assert props["themes"]["items"]["pattern"] == analyze.THEME_PATTERN
|
||
assert set(analyze.RESPONSE_SCHEMA["required"]) == set(props)
|
||
|
||
|
||
def test_lyrics_hash_changes_with_text():
|
||
assert analyze.lyrics_hash("текст") == analyze.lyrics_hash("текст")
|
||
assert analyze.lyrics_hash("текст") != analyze.lyrics_hash("другой текст")
|
||
|
||
|
||
def test_verify_key_line_accepts_verbatim_quote_ignoring_whitespace_and_case():
|
||
text = "Первая строка\nА я иду, шагаю по Москве\nТретья строка"
|
||
|
||
assert analyze.verify_key_line("А я иду, шагаю по Москве", text)
|
||
assert analyze.verify_key_line("а я иду, шагаю по москве", text)
|
||
|
||
|
||
def test_verify_key_line_rejects_paraphrase_and_empty():
|
||
text = "А я иду, шагаю по Москве"
|
||
|
||
assert not analyze.verify_key_line("Герой идёт по городу", text)
|
||
assert not analyze.verify_key_line("", text)
|
||
|
||
|
||
def test_is_fresh_requires_model_prompt_version_and_hash_match():
|
||
entry = {"model": "m1", "prompt_version": 1, "lyrics_hash": "h1"}
|
||
|
||
assert analyze.is_fresh(entry, "m1", 1, "h1")
|
||
assert not analyze.is_fresh(entry, "m2", 1, "h1")
|
||
assert not analyze.is_fresh(entry, "m1", 2, "h1")
|
||
assert not analyze.is_fresh(entry, "m1", 1, "h2")
|
||
assert not analyze.is_fresh(None, "m1", 1, "h1")
|
||
|
||
|
||
def test_is_fresh_false_for_error_marker():
|
||
# Ошибочные записи перезапрашиваются при следующем прогоне - это и есть механизм
|
||
# "повторить упавшие треки" вместо отдельного флага.
|
||
entry = {"model": "m1", "prompt_version": 1, "lyrics_hash": "h1", "error": "TimeoutError: ..."}
|
||
|
||
assert not analyze.is_fresh(entry, "m1", 1, "h1")
|
||
|
||
|
||
def test_top_themes_orders_by_frequency_and_limits():
|
||
cache = {
|
||
"1": {"themes": ["loneliness", "city"]},
|
||
"2": {"themes": ["loneliness", "memory"]},
|
||
"3": {"themes": ["loneliness", "city"]},
|
||
"4": {"error": "TimeoutError: ..."},
|
||
}
|
||
|
||
assert analyze.top_themes(cache, limit=2) == ["loneliness", "city"]
|
||
|
||
|
||
def test_load_analysis_returns_empty_dict_when_missing(tmp_path: Path):
|
||
assert analyze.load_analysis(tmp_path) == {}
|