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
376 lines
12 KiB
Python
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
|