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
387 lines
9.7 KiB
Python
387 lines
9.7 KiB
Python
"""
|
|
Tests for citation formatter error handling and malformed input scenarios.
|
|
"""
|
|
|
|
from local_deep_research.text_optimization.citation_formatter import (
|
|
CitationFormatter,
|
|
CitationMode,
|
|
)
|
|
|
|
|
|
class TestCitationErrorHandling:
|
|
"""Test error handling and resilience of citation formatter."""
|
|
|
|
def test_malformed_citation_brackets(self):
|
|
"""Test handling of malformed citation brackets."""
|
|
content = """# Research Report
|
|
|
|
Good citation [1], broken citation [2, missing citation ]3[,
|
|
reversed [4[ and double [[5]].
|
|
|
|
## Sources
|
|
|
|
[1] Good Paper
|
|
URL: https://example.com/1
|
|
|
|
[2] Second Paper
|
|
URL: https://example.com/2
|
|
|
|
[3] Third Paper
|
|
URL: https://example.com/3
|
|
|
|
[4] Fourth Paper
|
|
URL: https://example.com/4
|
|
|
|
[5] Fifth Paper
|
|
URL: https://example.com/5"""
|
|
|
|
formatter = CitationFormatter(mode=CitationMode.DOMAIN_HYPERLINKS)
|
|
result = formatter.format_document(content)
|
|
|
|
# Good citations should be formatted
|
|
assert "[example.com]" in result
|
|
|
|
# Malformed citations should be left as-is
|
|
assert "[2," in result # Broken bracket
|
|
assert "]3[" in result # Reversed brackets
|
|
assert "[4[" in result # Double opening
|
|
|
|
def test_invalid_citation_numbers(self):
|
|
"""Test handling of invalid citation numbers."""
|
|
content = """# Research Report
|
|
|
|
Valid [1], invalid [0], [999], [-1], [abc], [1.5], [ 2 ].
|
|
|
|
## Sources
|
|
|
|
[1] Only Valid Source
|
|
URL: https://example.com/valid
|
|
|
|
[2] Second Source
|
|
URL: https://example.com/2"""
|
|
|
|
formatter = CitationFormatter(mode=CitationMode.DOMAIN_HYPERLINKS)
|
|
result = formatter.format_document(content)
|
|
|
|
# Valid citation should be formatted
|
|
assert "[[example.com]](https://example.com/valid)" in result
|
|
|
|
# Invalid citations should remain
|
|
assert "[0]" in result
|
|
assert "[999]" in result
|
|
assert "[-1]" in result
|
|
assert "[abc]" in result
|
|
assert "[1.5]" in result
|
|
|
|
# Citation with spaces might be processed
|
|
# [ 2 ] with spaces is an edge case - formatter might or might not handle it
|
|
|
|
def test_circular_and_self_referencing(self):
|
|
"""Test handling of circular references in sources."""
|
|
content = """# Research Report
|
|
|
|
Citation [1] refers to [2].
|
|
|
|
## Sources
|
|
|
|
[1] First Paper [2]
|
|
URL: https://first.com
|
|
|
|
[2] Second Paper [1]
|
|
URL: https://second.com"""
|
|
|
|
formatter = CitationFormatter(mode=CitationMode.DOMAIN_HYPERLINKS)
|
|
result = formatter.format_document(content)
|
|
|
|
# Should format main text citations
|
|
assert "[first.com]" in result
|
|
assert "[second.com]" in result
|
|
|
|
# Should not format citations within source descriptions
|
|
sources_section = result.split("## Sources")[1]
|
|
assert "[1] First Paper [2]" in sources_section
|
|
assert "[2] Second Paper [1]" in sources_section
|
|
|
|
def test_missing_source_numbers(self):
|
|
"""Test handling when source numbers are missing."""
|
|
content = """# Research Report
|
|
|
|
Citations [1], [2], [3].
|
|
|
|
## Sources
|
|
|
|
[1] First Paper
|
|
URL: https://first.com
|
|
|
|
[3] Third Paper (note: [2] is missing)
|
|
URL: https://third.com"""
|
|
|
|
formatter = CitationFormatter(mode=CitationMode.DOMAIN_HYPERLINKS)
|
|
result = formatter.format_document(content)
|
|
|
|
# Existing sources should be formatted
|
|
assert "[first.com]" in result
|
|
assert "[third.com]" in result
|
|
|
|
# Missing source [2] should remain as-is
|
|
assert "[2]" in result
|
|
|
|
def test_duplicate_source_numbers(self):
|
|
"""Test handling of duplicate source numbers."""
|
|
content = """# Research Report
|
|
|
|
Citations [1] and another [1].
|
|
|
|
## Sources
|
|
|
|
[1] First Paper
|
|
URL: https://first.com
|
|
|
|
[1] Duplicate Paper
|
|
URL: https://duplicate.com
|
|
|
|
[2] Normal Paper
|
|
URL: https://second.com"""
|
|
|
|
formatter = CitationFormatter(mode=CitationMode.DOMAIN_HYPERLINKS)
|
|
result = formatter.format_document(content)
|
|
|
|
# Should handle duplicates (typically takes first occurrence)
|
|
# The specific behavior depends on implementation
|
|
assert "[first.com]" in result or "[duplicate.com]" in result
|
|
|
|
def test_sources_without_numbers(self):
|
|
"""Test handling of sources without proper numbering."""
|
|
content = """# Research Report
|
|
|
|
Citation [1].
|
|
|
|
## Sources
|
|
|
|
[1] Proper Source
|
|
URL: https://proper.com
|
|
|
|
Missing Number Source
|
|
URL: https://missing.com
|
|
|
|
[] Empty Brackets
|
|
URL: https://empty.com
|
|
|
|
[a] Letter Instead
|
|
URL: https://letter.com"""
|
|
|
|
formatter = CitationFormatter(mode=CitationMode.DOMAIN_HYPERLINKS)
|
|
result = formatter.format_document(content)
|
|
|
|
# Properly numbered source should work
|
|
assert "[proper.com]" in result
|
|
|
|
# Improperly formatted sources should be ignored
|
|
assert "[missing.com]" not in result
|
|
assert "[empty.com]" not in result
|
|
assert "[letter.com]" not in result
|
|
|
|
def test_nested_brackets_and_escaping(self):
|
|
"""Test handling of nested brackets and escaped characters."""
|
|
content = r"""# Research Report
|
|
|
|
Normal [1], escaped \[2\], nested [[3]], and markdown [link](url) [4].
|
|
|
|
## Sources
|
|
|
|
[1] First
|
|
URL: https://first.com
|
|
|
|
[2] Second
|
|
URL: https://second.com
|
|
|
|
[3] Third
|
|
URL: https://third.com
|
|
|
|
[4] Fourth
|
|
URL: https://fourth.com"""
|
|
|
|
formatter = CitationFormatter(mode=CitationMode.DOMAIN_HYPERLINKS)
|
|
result = formatter.format_document(content)
|
|
|
|
# Normal citation should work
|
|
assert "[first.com]" in result
|
|
|
|
# Escaped brackets should be preserved
|
|
assert r"\[2\]" in result
|
|
|
|
# Markdown links should not interfere
|
|
assert "[link](url)" in result
|
|
|
|
def test_malformed_urls(self):
|
|
"""Test handling of malformed URLs in sources."""
|
|
content = """# Research Report
|
|
|
|
Citations [1] [2] [3] [4] [5].
|
|
|
|
## Sources
|
|
|
|
[1] No Protocol
|
|
URL: example.com/paper
|
|
|
|
[2] Invalid Protocol
|
|
URL: htp://example.com/paper
|
|
|
|
[3] Just Domain
|
|
URL: https://example.com
|
|
|
|
[4] Spaces in URL
|
|
URL: https://example.com/my paper.pdf
|
|
|
|
[5] Missing URL Label
|
|
https://example.com/nolabel"""
|
|
|
|
formatter = CitationFormatter(mode=CitationMode.DOMAIN_HYPERLINKS)
|
|
result = formatter.format_document(content)
|
|
|
|
# Well-formed URLs should work
|
|
assert result.count("[example.com]") >= 1
|
|
|
|
# Malformed URLs might not be processed correctly
|
|
# The specific behavior depends on URL parsing implementation
|
|
|
|
def test_special_markdown_interference(self):
|
|
"""Test that special markdown doesn't interfere with citations."""
|
|
content = """# Research Report
|
|
|
|
**Bold [1]**, *italic [2]*, `code [3]`, ~~strike [4]~~.
|
|
|
|
> Quote [5]
|
|
> Multiple lines
|
|
|
|
| Table | Cell [6] |
|
|
|-------|----------|
|
|
| Data | [7] |
|
|
|
|
## Sources
|
|
|
|
[1] Bold Source
|
|
URL: https://bold.com
|
|
|
|
[2] Italic Source
|
|
URL: https://italic.com
|
|
|
|
[3] Code Source
|
|
URL: https://code.com
|
|
|
|
[4] Strike Source
|
|
URL: https://strike.com
|
|
|
|
[5] Quote Source
|
|
URL: https://quote.com
|
|
|
|
[6] Table Source
|
|
URL: https://table.com
|
|
|
|
[7] Cell Source
|
|
URL: https://cell.com"""
|
|
|
|
formatter = CitationFormatter(mode=CitationMode.DOMAIN_HYPERLINKS)
|
|
result = formatter.format_document(content)
|
|
|
|
# All citations should be formatted despite markdown
|
|
domains = [
|
|
"bold.com",
|
|
"italic.com",
|
|
"code.com",
|
|
"strike.com",
|
|
"quote.com",
|
|
"table.com",
|
|
"cell.com",
|
|
]
|
|
|
|
for domain in domains:
|
|
assert f"[{domain}]" in result
|
|
|
|
def test_extremely_long_content(self):
|
|
"""Test handling of very long documents."""
|
|
# Create a document with many citations
|
|
citations = []
|
|
sources = []
|
|
|
|
for i in range(1, 101):
|
|
citations.append(f"Citation [{i}]")
|
|
sources.append(f"""[{i}] Source {i}
|
|
URL: https://example{i}.com/paper""")
|
|
|
|
content = f"""# Large Document
|
|
|
|
{" ".join(citations)}
|
|
|
|
## Sources
|
|
|
|
{chr(10).join(sources)}"""
|
|
|
|
formatter = CitationFormatter(mode=CitationMode.DOMAIN_ID_HYPERLINKS)
|
|
result = formatter.format_document(content)
|
|
|
|
# Should handle all citations
|
|
for i in range(1, 101):
|
|
assert f"[example{i}.com" in result
|
|
|
|
def test_source_url_edge_cases(self):
|
|
"""Test edge cases in URL detection within sources."""
|
|
content = """# Research Report
|
|
|
|
Citations [1] [2] [3].
|
|
|
|
## Sources
|
|
|
|
[1] Multiple URLs
|
|
URL: https://first.com
|
|
Also available at: https://mirror.com
|
|
|
|
[2] URL in Middle
|
|
Paper Title URL: https://second.com more text
|
|
|
|
[3] Commented URL
|
|
# URL: https://commented.com
|
|
URL: https://actual.com"""
|
|
|
|
formatter = CitationFormatter(mode=CitationMode.DOMAIN_HYPERLINKS)
|
|
result = formatter.format_document(content)
|
|
|
|
# Should pick up the first URL after "URL:"
|
|
assert "[first.com]" in result
|
|
assert "[commented.com]" not in result
|
|
|
|
# URLs with text after them on the same line are not captured
|
|
# This is a known limitation of the current regex pattern
|
|
assert "[second.com]" not in result
|
|
|
|
# URL after a comment line is also not captured
|
|
# The formatter only captures URLs immediately after the source title
|
|
assert "[actual.com]" not in result
|
|
|
|
def test_mixed_citation_styles(self):
|
|
"""Test documents with mixed citation styles."""
|
|
content = """# Research Report
|
|
|
|
IEEE style [1], parenthetical (Smith, 2023), footnote[^1], and [2].
|
|
|
|
## Sources
|
|
|
|
[1] First Paper
|
|
URL: https://first.com
|
|
|
|
[2] Second Paper
|
|
URL: https://second.com
|
|
|
|
[^1]: This is a footnote, not a citation source."""
|
|
|
|
formatter = CitationFormatter(mode=CitationMode.DOMAIN_HYPERLINKS)
|
|
result = formatter.format_document(content)
|
|
|
|
# Should only format numbered citations
|
|
assert "[first.com]" in result
|
|
assert "[second.com]" in result
|
|
|
|
# Other citation styles should remain
|
|
assert "(Smith, 2023)" in result
|
|
assert "[^1]" in result
|