Files
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

523 lines
18 KiB
Python

"""
Tests for report_generator.py - Section Generation and State Management
Tests cover the _research_and_generate_sections() method which:
- Initializes questions from previous iterations
- Manages search system state between subsections
- Restores max_iterations after errors
- Creates default subsections when none provided
"""
from unittest.mock import MagicMock, patch
import pytest
class TestSectionGenerationStateManagement:
"""Tests for state management during section generation."""
@pytest.fixture
def report_generator(self):
"""Create a report generator with mocked dependencies."""
with patch("local_deep_research.report_generator.AdvancedSearchSystem"):
with patch(
"local_deep_research.report_generator.get_llm"
) as mock_get_llm:
mock_llm = MagicMock()
mock_get_llm.return_value = mock_llm
# Create a mock search system with necessary attributes
mock_search_system = MagicMock()
mock_search_system.all_links_of_system = []
mock_search_system.max_iterations = 3
mock_search_system.questions_by_iteration = {}
mock_search_system.analyze_topic.return_value = {
"current_knowledge": "Generated content for section"
}
# Create a mock strategy
mock_strategy = MagicMock()
mock_strategy.questions_by_iteration = {}
mock_strategy.settings_snapshot = {"search.iterations": 3}
mock_strategy.max_iterations = 3
mock_search_system.strategy = mock_strategy
from local_deep_research.report_generator import (
IntegratedReportGenerator,
)
generator = IntegratedReportGenerator(llm=mock_llm)
generator.search_system = mock_search_system
yield generator
def test_questions_preserved_from_initial_findings(self, report_generator):
"""Questions from initial research should be passed to search system."""
initial_findings = {
"current_knowledge": "test content",
"questions_by_iteration": {
0: ["Q1: What is the topic?", "Q2: How does it work?"],
1: ["Q3: What are the applications?"],
},
}
structure = [
{
"name": "Introduction",
"subsections": [
{"name": "Overview", "purpose": "Intro purpose"}
],
}
]
report_generator._research_and_generate_sections(
initial_findings, structure, "test query"
)
# Verify questions were copied to strategy
assert (
report_generator.search_system.strategy.questions_by_iteration
== initial_findings["questions_by_iteration"]
)
def test_empty_questions_handled_gracefully(self, report_generator):
"""Empty questions_by_iteration should not cause errors."""
initial_findings = {
"current_knowledge": "test content",
"questions_by_iteration": {},
}
structure = [
{
"name": "Test Section",
"subsections": [{"name": "Sub", "purpose": "Purpose"}],
}
]
# Should not raise any exception
sections = report_generator._research_and_generate_sections(
initial_findings, structure, "test query"
)
assert "Test Section" in sections
def test_missing_questions_key_handled(self, report_generator):
"""Missing questions_by_iteration key should not cause errors."""
initial_findings = {"current_knowledge": "test content"}
structure = [
{
"name": "Test Section",
"subsections": [{"name": "Sub", "purpose": "Purpose"}],
}
]
# Should not raise any exception
sections = report_generator._research_and_generate_sections(
initial_findings, structure, "test query"
)
assert "Test Section" in sections
class TestSectionGenerationEmptySubsections:
"""Tests for handling sections without subsections."""
@pytest.fixture
def report_generator(self):
"""Create a report generator with mocked dependencies."""
with patch("local_deep_research.report_generator.AdvancedSearchSystem"):
with patch(
"local_deep_research.report_generator.get_llm"
) as mock_get_llm:
mock_llm = MagicMock()
mock_get_llm.return_value = mock_llm
mock_search_system = MagicMock()
mock_search_system.all_links_of_system = []
mock_search_system.max_iterations = 3
mock_search_system.analyze_topic.return_value = {
"current_knowledge": "Generated content"
}
from local_deep_research.report_generator import (
IntegratedReportGenerator,
)
generator = IntegratedReportGenerator(llm=mock_llm)
generator.search_system = mock_search_system
yield generator
def test_section_with_empty_subsections_creates_default(
self, report_generator
):
"""Section with empty subsections list should get a default subsection."""
structure = [{"name": "Standalone Section", "subsections": []}]
initial_findings = {
"current_knowledge": "test",
"questions_by_iteration": {},
}
sections = report_generator._research_and_generate_sections(
initial_findings, structure, "test query"
)
assert "Standalone Section" in sections
# analyze_topic should have been called for the auto-created subsection
report_generator.search_system.analyze_topic.assert_called()
def test_section_with_pipe_in_name_parsed_for_subsection(
self, report_generator
):
"""Section name with pipe should be parsed into subsection name and purpose."""
structure = [
{"name": "Main Topic | Purpose of this section", "subsections": []}
]
initial_findings = {
"current_knowledge": "test",
"questions_by_iteration": {},
}
sections = report_generator._research_and_generate_sections(
initial_findings, structure, "test query"
)
# The section should be processed with subsections created from the pipe-split name
assert "Main Topic | Purpose of this section" in sections
def test_multiple_empty_sections_each_get_default(self, report_generator):
"""Multiple sections without subsections each get their own default."""
structure = [
{"name": "Section A", "subsections": []},
{"name": "Section B", "subsections": []},
{"name": "Section C", "subsections": []},
]
initial_findings = {
"current_knowledge": "test",
"questions_by_iteration": {},
}
sections = report_generator._research_and_generate_sections(
initial_findings, structure, "test query"
)
assert len(sections) == 3
assert "Section A" in sections
assert "Section B" in sections
assert "Section C" in sections
class TestMaxIterationsRestoration:
"""Tests for max_iterations preservation and restoration."""
@pytest.fixture
def report_generator(self):
"""Create a report generator with mocked dependencies."""
with patch("local_deep_research.report_generator.AdvancedSearchSystem"):
with patch(
"local_deep_research.report_generator.get_llm"
) as mock_get_llm:
mock_llm = MagicMock()
mock_get_llm.return_value = mock_llm
mock_search_system = MagicMock()
mock_search_system.all_links_of_system = []
mock_search_system.max_iterations = 5 # Original value
mock_search_system.analyze_topic.return_value = {
"current_knowledge": "Content"
}
# Create a mock strategy with real settings_snapshot dict
mock_strategy = MagicMock()
mock_strategy.settings_snapshot = {"search.iterations": 5}
mock_strategy.max_iterations = 5
mock_search_system.strategy = mock_strategy
from local_deep_research.report_generator import (
IntegratedReportGenerator,
)
generator = IntegratedReportGenerator(llm=mock_llm)
generator.search_system = mock_search_system
yield generator
def test_max_iterations_restored_after_section(self, report_generator):
"""max_iterations should be restored to original value after each subsection."""
original_max = report_generator.search_system.max_iterations
structure = [
{
"name": "Test",
"subsections": [{"name": "Sub", "purpose": "Purpose"}],
}
]
report_generator._research_and_generate_sections(
{"current_knowledge": "test", "questions_by_iteration": {}},
structure,
"query",
)
# After generation, max_iterations should be back to original
assert report_generator.search_system.max_iterations == original_max
def test_max_iterations_set_to_one_during_subsection_research(
self, report_generator
):
"""search.iterations should be set to 1 in strategy settings_snapshot during subsection research."""
iterations_during_search = []
def capture_iterations(*args, **kwargs):
iterations_during_search.append(
report_generator.search_system.strategy.settings_snapshot.get(
"search.iterations"
)
)
return {"current_knowledge": "Content"}
report_generator.search_system.analyze_topic.side_effect = (
capture_iterations
)
structure = [
{
"name": "Test",
"subsections": [
{"name": "Sub1", "purpose": "P1"},
{"name": "Sub2", "purpose": "P2"},
],
}
]
report_generator._research_and_generate_sections(
{"current_knowledge": "test", "questions_by_iteration": {}},
structure,
"query",
)
# Each subsection should have had search.iterations=1
assert all(i == 1 for i in iterations_during_search)
def test_max_iterations_restored_after_multiple_sections(
self, report_generator
):
"""max_iterations restoration should work across multiple sections."""
original_max = report_generator.search_system.max_iterations
structure = [
{
"name": "Section1",
"subsections": [{"name": "Sub1", "purpose": "P1"}],
},
{
"name": "Section2",
"subsections": [
{"name": "Sub2a", "purpose": "P2a"},
{"name": "Sub2b", "purpose": "P2b"},
],
},
]
report_generator._research_and_generate_sections(
{"current_knowledge": "test", "questions_by_iteration": {}},
structure,
"query",
)
assert report_generator.search_system.max_iterations == original_max
class TestStateIsolationBetweenSections:
"""Tests for ensuring state doesn't leak between sections."""
@pytest.fixture
def report_generator(self):
"""Create a report generator with mocked dependencies."""
with patch("local_deep_research.report_generator.AdvancedSearchSystem"):
with patch(
"local_deep_research.report_generator.get_llm"
) as mock_get_llm:
mock_llm = MagicMock()
mock_get_llm.return_value = mock_llm
mock_search_system = MagicMock()
mock_search_system.all_links_of_system = []
mock_search_system.max_iterations = 3
mock_search_system.analyze_topic.return_value = {
"current_knowledge": "Content"
}
from local_deep_research.report_generator import (
IntegratedReportGenerator,
)
generator = IntegratedReportGenerator(llm=mock_llm)
generator.search_system = mock_search_system
yield generator
def test_sections_content_independent(self, report_generator):
"""Each section should receive independent content."""
call_count = [0]
def unique_content(*args, **kwargs):
call_count[0] += 1
return {"current_knowledge": f"Content for call {call_count[0]}"}
report_generator.search_system.analyze_topic.side_effect = (
unique_content
)
structure = [
{
"name": "Section1",
"subsections": [{"name": "Sub1", "purpose": "P1"}],
},
{
"name": "Section2",
"subsections": [{"name": "Sub2", "purpose": "P2"}],
},
]
sections = report_generator._research_and_generate_sections(
{"current_knowledge": "test", "questions_by_iteration": {}},
structure,
"query",
)
# Each section should have different content
assert "Content for call 1" in sections["Section1"]
assert "Content for call 2" in sections["Section2"]
def test_context_includes_other_sections(self, report_generator):
"""Each subsection query should include context about other sections."""
captured_queries = []
def capture_query(query, *args, **kwargs):
captured_queries.append(query)
return {"current_knowledge": "Content"}
report_generator.search_system.analyze_topic.side_effect = capture_query
structure = [
{
"name": "Introduction",
"subsections": [{"name": "Overview", "purpose": "Intro"}],
},
{
"name": "Main Content",
"subsections": [{"name": "Details", "purpose": "Main info"}],
},
{
"name": "Conclusion",
"subsections": [{"name": "Summary", "purpose": "Wrap up"}],
},
]
report_generator._research_and_generate_sections(
{"current_knowledge": "test", "questions_by_iteration": {}},
structure,
"test query",
)
# First section query should mention other sections
assert (
"Main Content" in captured_queries[0]
or "Conclusion" in captured_queries[0]
)
# Middle section should mention Introduction and Conclusion
assert (
"Introduction" in captured_queries[1]
or "Conclusion" in captured_queries[1]
)
class TestEmptyResultHandling:
"""Tests for handling empty or missing content from search system."""
@pytest.fixture
def report_generator(self):
"""Create a report generator with mocked dependencies."""
with patch("local_deep_research.report_generator.AdvancedSearchSystem"):
with patch(
"local_deep_research.report_generator.get_llm"
) as mock_get_llm:
mock_llm = MagicMock()
mock_get_llm.return_value = mock_llm
mock_search_system = MagicMock()
mock_search_system.all_links_of_system = []
mock_search_system.max_iterations = 3
from local_deep_research.report_generator import (
IntegratedReportGenerator,
)
generator = IntegratedReportGenerator(llm=mock_llm)
generator.search_system = mock_search_system
yield generator
def test_empty_current_knowledge_shows_placeholder(self, report_generator):
"""Empty current_knowledge should result in placeholder text."""
report_generator.search_system.analyze_topic.return_value = {
"current_knowledge": ""
}
structure = [
{
"name": "Test",
"subsections": [{"name": "Sub", "purpose": "Purpose"}],
}
]
sections = report_generator._research_and_generate_sections(
{"current_knowledge": "test", "questions_by_iteration": {}},
structure,
"query",
)
assert "Limited information was found" in sections["Test"]
def test_none_current_knowledge_shows_placeholder(self, report_generator):
"""None current_knowledge should result in placeholder text."""
report_generator.search_system.analyze_topic.return_value = {
"current_knowledge": None
}
structure = [
{
"name": "Test",
"subsections": [{"name": "Sub", "purpose": "Purpose"}],
}
]
sections = report_generator._research_and_generate_sections(
{"current_knowledge": "test", "questions_by_iteration": {}},
structure,
"query",
)
assert "Limited information was found" in sections["Test"]
def test_missing_current_knowledge_key_shows_placeholder(
self, report_generator
):
"""Missing current_knowledge key should result in placeholder text."""
report_generator.search_system.analyze_topic.return_value = {}
structure = [
{
"name": "Test",
"subsections": [{"name": "Sub", "purpose": "Purpose"}],
}
]
sections = report_generator._research_and_generate_sections(
{"current_knowledge": "test", "questions_by_iteration": {}},
structure,
"query",
)
assert "Limited information was found" in sections["Test"]