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
161 lines
5.8 KiB
Python
161 lines
5.8 KiB
Python
"""Edge-case tests for report_generator — structure parsing boundaries."""
|
|
|
|
from unittest.mock import Mock
|
|
|
|
import pytest
|
|
|
|
from local_deep_research.report_generator import IntegratedReportGenerator
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_search_system():
|
|
"""Create a mock search system."""
|
|
mock = Mock()
|
|
mock.all_links_of_system = []
|
|
return mock
|
|
|
|
|
|
@pytest.fixture
|
|
def generator(mock_llm, mock_search_system, monkeypatch):
|
|
"""Create a report generator with mocked dependencies."""
|
|
monkeypatch.setattr(
|
|
"local_deep_research.report_generator.get_llm", lambda: mock_llm
|
|
)
|
|
return IntegratedReportGenerator(
|
|
search_system=mock_search_system, llm=mock_llm
|
|
)
|
|
|
|
|
|
class TestDetermineReportStructureNoDelimiters:
|
|
"""Verify LLM response without STRUCTURE/END_STRUCTURE returns empty structure."""
|
|
|
|
def test_no_structure_delimiters(self, generator):
|
|
"""Response without STRUCTURE markers → empty structure list."""
|
|
generator.model.invoke.return_value = Mock(
|
|
content="Here is my analysis of the topic. It covers many areas."
|
|
)
|
|
|
|
findings = {"current_knowledge": "Some knowledge about the topic."}
|
|
structure = generator._determine_report_structure(
|
|
findings, "test query"
|
|
)
|
|
|
|
# No numbered section lines → empty structure
|
|
assert structure == []
|
|
|
|
|
|
class TestDetermineReportStructureSectionWithoutPeriod:
|
|
"""A numbered section line without a period is skipped gracefully (no IndexError)."""
|
|
|
|
def test_section_without_period_is_skipped(self, generator):
|
|
"""'1 Section Name' (no period) is skipped rather than raising IndexError."""
|
|
generator.model.invoke.return_value = Mock(
|
|
content=("STRUCTURE\n1 Section Without Period\nEND_STRUCTURE")
|
|
)
|
|
|
|
findings = {"current_knowledge": "Some knowledge."}
|
|
# Previously line.split(".")[1] raised IndexError; now the line is skipped.
|
|
structure = generator._determine_report_structure(
|
|
findings, "test query"
|
|
)
|
|
assert structure == []
|
|
|
|
def test_valid_and_dotless_lines_mixed(self, generator):
|
|
"""A valid numbered line still parses even when a dotless one precedes it."""
|
|
generator.model.invoke.return_value = Mock(
|
|
content=(
|
|
"STRUCTURE\n1 No Period Here\n2. Real Section\nEND_STRUCTURE"
|
|
)
|
|
)
|
|
|
|
findings = {"current_knowledge": "Some knowledge."}
|
|
structure = generator._determine_report_structure(
|
|
findings, "test query"
|
|
)
|
|
assert len(structure) == 1
|
|
assert structure[0]["name"] == "Real Section"
|
|
|
|
|
|
class TestDetermineReportStructureSubsectionBeforeAnySection:
|
|
"""Verify subsection line before any numbered section doesn't crash."""
|
|
|
|
def test_subsection_before_any_section(self, generator):
|
|
"""'- Subsection' before any numbered section → current_section is None."""
|
|
generator.model.invoke.return_value = Mock(
|
|
content=(
|
|
"STRUCTURE\n"
|
|
"- Orphan Subsection | some purpose\n"
|
|
"1. Actual Section\n"
|
|
" - Valid Subsection | valid purpose\n"
|
|
"END_STRUCTURE"
|
|
)
|
|
)
|
|
|
|
findings = {"current_knowledge": "Some knowledge."}
|
|
structure = generator._determine_report_structure(
|
|
findings, "test query"
|
|
)
|
|
|
|
# The orphan subsection should be safely skipped (current_section guard)
|
|
# Only the actual section with its valid subsection should be parsed
|
|
assert len(structure) == 1
|
|
assert structure[0]["name"] == "Actual Section"
|
|
assert len(structure[0]["subsections"]) == 1
|
|
assert structure[0]["subsections"][0]["name"] == "Valid Subsection"
|
|
|
|
|
|
class TestDetermineReportStructureRemovesSourcesSection:
|
|
"""Verify last section named 'References' or 'Sources' is auto-removed."""
|
|
|
|
def test_removes_references_section(self, generator):
|
|
"""Last section named 'References' should be auto-removed."""
|
|
generator.model.invoke.return_value = Mock(
|
|
content=(
|
|
"STRUCTURE\n"
|
|
"1. Introduction\n"
|
|
" - Overview | provide overview\n"
|
|
"2. Key Findings\n"
|
|
" - Results | present results\n"
|
|
"3. References\n"
|
|
" - Bibliography | list sources\n"
|
|
"END_STRUCTURE"
|
|
)
|
|
)
|
|
|
|
findings = {"current_knowledge": "Some knowledge."}
|
|
structure = generator._determine_report_structure(
|
|
findings, "test query"
|
|
)
|
|
|
|
# "References" matches source_keywords → removed
|
|
assert len(structure) == 2
|
|
assert structure[0]["name"] == "Introduction"
|
|
assert structure[1]["name"] == "Key Findings"
|
|
# "References" section should be gone
|
|
section_names = [s["name"] for s in structure]
|
|
assert "References" not in section_names
|
|
|
|
def test_does_not_remove_non_last_source_section(self, generator):
|
|
"""Source-related section that is NOT last should be kept."""
|
|
generator.model.invoke.return_value = Mock(
|
|
content=(
|
|
"STRUCTURE\n"
|
|
"1. Sources Overview\n"
|
|
" - Data Sources | describe data origins\n"
|
|
"2. Analysis\n"
|
|
" - Methods | explain methods\n"
|
|
"END_STRUCTURE"
|
|
)
|
|
)
|
|
|
|
findings = {"current_knowledge": "Some knowledge."}
|
|
structure = generator._determine_report_structure(
|
|
findings, "test query"
|
|
)
|
|
|
|
# "Sources Overview" is first, not last → should NOT be removed
|
|
# "Analysis" is last and doesn't match source keywords → kept
|
|
assert len(structure) == 2
|
|
assert structure[0]["name"] == "Sources Overview"
|
|
assert structure[1]["name"] == "Analysis"
|