"""Tests for _validate_topics() pure logic in topic_generator. Tests cover: - Length filtering (min 2, max 30) - Case-insensitive deduplication - Whitespace stripping - max_topics enforcement - Empty/falsy topic handling - Output normalization to lowercase """ from local_deep_research.news.utils.topic_generator import _validate_topics class TestValidateTopics: """Tests for _validate_topics().""" # -- basic functionality ------------------------------------------- def test_valid_topics_pass_through(self): result = _validate_topics(["AI", "Climate"], max_topics=5) assert result == ["ai", "climate"] def test_output_normalized_to_lowercase(self): result = _validate_topics( ["CLIMATE Change", "AI Research"], max_topics=5 ) assert result == ["climate change", "ai research"] # -- length filtering ---------------------------------------------- def test_single_char_topics_filtered(self): result = _validate_topics(["a", "AI", "b"], max_topics=5) assert result == ["ai"] def test_exactly_two_char_topic_kept(self): result = _validate_topics(["AI"], max_topics=5) assert result == ["ai"] def test_topics_over_30_chars_filtered(self): long_topic = "a" * 31 result = _validate_topics([long_topic, "valid"], max_topics=5) assert result == ["valid"] def test_exactly_30_char_topic_kept(self): topic = "a" * 30 result = _validate_topics([topic], max_topics=5) assert result == [topic] # -- deduplication ------------------------------------------------- def test_case_insensitive_dedup(self): result = _validate_topics(["AI", "ai", "Ai", "aI"], max_topics=5) assert result == ["ai"] def test_dedup_preserves_first_occurrence_normalized(self): result = _validate_topics( ["Climate", "Economy", "climate"], max_topics=5 ) assert result == ["climate", "economy"] # -- whitespace handling ------------------------------------------- def test_leading_trailing_whitespace_stripped(self): result = _validate_topics([" AI ", " Climate "], max_topics=5) assert result == ["ai", "climate"] def test_whitespace_only_topic_filtered(self): result = _validate_topics([" ", "valid"], max_topics=5) assert result == ["valid"] # -- empty / falsy handling ---------------------------------------- def test_empty_string_filtered(self): result = _validate_topics(["", "valid"], max_topics=5) assert result == ["valid"] def test_none_topic_filtered(self): """None values should be skipped (falsy check).""" result = _validate_topics([None, "valid"], max_topics=5) assert result == ["valid"] def test_all_invalid_returns_fallback(self): result = _validate_topics(["", "a", None], max_topics=5) assert result == ["[No valid topics]"] def test_empty_list_returns_fallback(self): result = _validate_topics([], max_topics=5) assert result == ["[No valid topics]"] # -- max_topics enforcement ---------------------------------------- def test_max_topics_limits_output(self): topics = ["one", "two", "three", "four", "five"] result = _validate_topics(topics, max_topics=3) assert len(result) == 3 assert result == ["one", "two", "three"] def test_max_topics_one(self): result = _validate_topics(["first", "second"], max_topics=1) assert result == ["first"] def test_fewer_topics_than_max(self): result = _validate_topics(["one", "two"], max_topics=10) assert result == ["one", "two"] # -- combined edge cases ------------------------------------------- def test_dedup_then_max_topics(self): """Dedup reduces count, max_topics applied after.""" topics = ["AI", "ai", "Climate", "climate", "Economy"] result = _validate_topics(topics, max_topics=2) assert result == ["ai", "climate"] def test_length_filter_then_dedup(self): """Short topics filtered before dedup counting.""" topics = ["a", "AI", "AI"] result = _validate_topics(topics, max_topics=5) assert result == ["ai"]