Files
learningcircuit--local-deep…/tests/news/test_topic_generator_extended.py
wehub-resource-sync 7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

376 lines
12 KiB
Python

"""
Extended tests for news/utils/topic_generator.py
Tests cover:
- generate_topics() function
- _generate_with_llm() helper
- _validate_topics() helper
- Topic cleaning and validation
- Edge cases and error handling
"""
from unittest.mock import Mock, patch
class TestGenerateTopicsBasic:
"""Basic tests for generate_topics() function."""
def test_returns_list(self):
"""generate_topics returns a list."""
with patch(
"local_deep_research.news.utils.topic_generator._generate_with_llm"
) as mock_gen:
mock_gen.return_value = ["Topic 1", "Topic 2"]
from local_deep_research.news.utils.topic_generator import (
generate_topics,
)
result = generate_topics("test query")
assert isinstance(result, list)
def test_uses_llm_generated_topics(self):
"""Uses LLM-generated topics when available."""
with patch(
"local_deep_research.news.utils.topic_generator._generate_with_llm"
) as mock_gen:
mock_gen.return_value = ["AI", "Technology", "Research"]
from local_deep_research.news.utils.topic_generator import (
generate_topics,
)
result = generate_topics("query", "findings")
# Topics get lowercased by _validate_topics
assert "ai" in result or "AI" in result
def test_returns_failure_message_when_llm_fails(self):
"""Returns failure message when LLM fails."""
with patch(
"local_deep_research.news.utils.topic_generator._generate_with_llm"
) as mock_gen:
mock_gen.return_value = []
from local_deep_research.news.utils.topic_generator import (
generate_topics,
)
result = generate_topics("query", "findings")
assert any(
"failed" in t.lower() or "no valid" in t.lower() for t in result
)
def test_accepts_category_parameter(self):
"""Accepts category parameter."""
with patch(
"local_deep_research.news.utils.topic_generator._generate_with_llm"
) as mock_gen:
mock_gen.return_value = ["Tech News"]
from local_deep_research.news.utils.topic_generator import (
generate_topics,
)
generate_topics("query", "findings", category="technology")
mock_gen.assert_called_once()
def test_respects_max_topics_parameter(self):
"""Respects max_topics parameter."""
with patch(
"local_deep_research.news.utils.topic_generator._generate_with_llm"
) as mock_gen:
mock_gen.return_value = ["AA", "BB", "CC", "DD", "EE", "FF"]
from local_deep_research.news.utils.topic_generator import (
generate_topics,
)
result = generate_topics("query", max_topics=3)
assert len(result) <= 3
class TestGenerateWithLLMFunction:
"""Tests for _generate_with_llm() helper function."""
def test_function_exists(self):
"""_generate_with_llm function exists."""
from local_deep_research.news.utils.topic_generator import (
_generate_with_llm,
)
assert callable(_generate_with_llm)
def test_returns_list(self):
"""_generate_with_llm returns a list."""
with patch(
"local_deep_research.config.llm_config.get_llm"
) as mock_get_llm:
mock_llm = Mock()
mock_llm.invoke.return_value = Mock(
content='["Topic 1", "Topic 2"]'
)
mock_get_llm.return_value = mock_llm
from local_deep_research.news.utils.topic_generator import (
_generate_with_llm,
)
result = _generate_with_llm("query", "findings", "", 5)
# May return list or empty list depending on LLM response
assert isinstance(result, list)
def test_returns_empty_on_error(self):
"""_generate_with_llm returns empty list on error."""
with patch(
"local_deep_research.config.llm_config.get_llm"
) as mock_get_llm:
mock_get_llm.side_effect = Exception("LLM error")
from local_deep_research.news.utils.topic_generator import (
_generate_with_llm,
)
result = _generate_with_llm("query", "findings", "", 5)
assert result == []
class TestValidateTopics:
"""Tests for _validate_topics() helper."""
def test_removes_empty_topics(self):
"""Removes empty string topics."""
from local_deep_research.news.utils.topic_generator import (
_validate_topics,
)
result = _validate_topics(["Topic", "", " ", "Valid"], 5)
assert "" not in result
assert " " not in result
def test_removes_short_topics(self):
"""Removes topics shorter than 2 characters."""
from local_deep_research.news.utils.topic_generator import (
_validate_topics,
)
result = _validate_topics(["AI", "A", "AB", "ABC"], 5)
assert "a" not in result # Single char removed
assert "ab" in result # 2 chars kept
assert "abc" in result # 3 chars kept
def test_removes_long_topics(self):
"""Removes topics longer than 30 characters."""
from local_deep_research.news.utils.topic_generator import (
_validate_topics,
)
long_topic = "A" * 35
result = _validate_topics(["Short", long_topic], 5)
assert "short" in result
assert long_topic.lower() not in result
def test_removes_duplicates_case_insensitive(self):
"""Removes duplicate topics (case-insensitive)."""
from local_deep_research.news.utils.topic_generator import (
_validate_topics,
)
result = _validate_topics(["AI", "ai", "Ai", "Technology"], 5)
assert result.count("ai") == 1
def test_converts_to_lowercase(self):
"""Converts topics to lowercase."""
from local_deep_research.news.utils.topic_generator import (
_validate_topics,
)
result = _validate_topics(["TECHNOLOGY", "Science"], 5)
assert "technology" in result
assert "science" in result
assert "TECHNOLOGY" not in result
def test_respects_max_topics(self):
"""Respects max_topics limit."""
from local_deep_research.news.utils.topic_generator import (
_validate_topics,
)
result = _validate_topics(
["A1", "B2", "C3", "D4", "E5", "F6", "G7"], max_topics=3
)
assert len(result) == 3
def test_returns_no_valid_topics_message(self):
"""Returns failure message when no valid topics."""
from local_deep_research.news.utils.topic_generator import (
_validate_topics,
)
result = _validate_topics(["", " ", "A"], 5) # All invalid
assert "[No valid topics]" in result
class TestTopicGeneratorEdgeCases:
"""Edge case tests for topic generation."""
def test_empty_query(self):
"""Handles empty query."""
with patch(
"local_deep_research.news.utils.topic_generator._generate_with_llm"
) as mock_gen:
mock_gen.return_value = []
from local_deep_research.news.utils.topic_generator import (
generate_topics,
)
result = generate_topics("")
assert isinstance(result, list)
def test_unicode_topics(self):
"""Handles unicode topics."""
from local_deep_research.news.utils.topic_generator import (
_validate_topics,
)
result = _validate_topics(["日本", "économie", "科技"], 5)
assert "日本" in result
assert "économie" in result
def test_topics_with_special_characters(self):
"""Handles topics with special characters."""
from local_deep_research.news.utils.topic_generator import (
_validate_topics,
)
result = _validate_topics(["AI-ML", "COVID-19", "C++"], 5)
# Topics are cleaned and lowercased
assert any("ai" in t or "ml" in t for t in result) or len(result) > 0
def test_max_topics_zero(self):
"""Handles max_topics of zero."""
from local_deep_research.news.utils.topic_generator import (
_validate_topics,
)
result = _validate_topics(["Topic 1", "Topic 2"], 0)
# Should return no valid topics message or empty behavior
assert len(result) <= 1
class TestTopicGeneratorIntegration:
"""Integration tests for topic generation."""
def test_full_flow_with_llm_mock(self):
"""Full flow with mocked LLM generation."""
with patch(
"local_deep_research.news.utils.topic_generator._generate_with_llm"
) as mock_gen:
mock_gen.return_value = [
"Artificial Intelligence",
"Machine Learning",
"Technology",
]
from local_deep_research.news.utils.topic_generator import (
generate_topics,
)
result = generate_topics(
"AI research news",
"Recent developments in AI and machine learning...",
category="technology",
max_topics=5,
)
assert len(result) > 0
assert "artificial intelligence" in result
def test_full_flow_with_llm_failure(self):
"""Full flow when LLM fails."""
with patch(
"local_deep_research.news.utils.topic_generator._generate_with_llm"
) as mock_gen:
mock_gen.return_value = [] # LLM failed, returns empty
from local_deep_research.news.utils.topic_generator import (
generate_topics,
)
result = generate_topics("query", "findings")
# Should return failure message - either "failed" or "no valid"
assert isinstance(result, list)
assert len(result) > 0
has_failure_indicator = any(
"failed" in t.lower() or "no valid" in t.lower() or "[" in t
for t in result
)
assert has_failure_indicator
class TestTopicValidationDetails:
"""Detailed tests for topic validation."""
def test_strips_whitespace(self):
"""Strips whitespace from topics."""
from local_deep_research.news.utils.topic_generator import (
_validate_topics,
)
result = _validate_topics([" Topic ", "\tAnother\n"], 5)
assert "topic" in result
assert "another" in result
def test_preserves_order(self):
"""Preserves order of topics."""
from local_deep_research.news.utils.topic_generator import (
_validate_topics,
)
result = _validate_topics(["First", "Second", "Third"], 5)
assert result[0] == "first"
assert result[1] == "second"
assert result[2] == "third"
def test_handles_only_invalid_topics(self):
"""Handles list with only invalid topics."""
from local_deep_research.news.utils.topic_generator import (
_validate_topics,
)
result = _validate_topics(["", " ", "X"], 5)
# All invalid - should get failure message
assert "[No valid topics]" in result
def test_exact_max_topics_boundary(self):
"""Handles exactly max_topics number of topics."""
from local_deep_research.news.utils.topic_generator import (
_validate_topics,
)
result = _validate_topics(["AA", "BB", "CC"], 3)
assert len(result) == 3