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
335 lines
10 KiB
Python
335 lines
10 KiB
Python
"""
|
|
Tests for citation export formats (RIS, LaTeX, Quarto) to ensure
|
|
they handle edge cases correctly.
|
|
"""
|
|
|
|
import pytest
|
|
from local_deep_research.text_optimization.citation_formatter import (
|
|
CitationFormatter,
|
|
CitationMode,
|
|
RISExporter,
|
|
LaTeXExporter,
|
|
QuartoExporter,
|
|
)
|
|
|
|
|
|
class TestCitationExportFormats:
|
|
"""Test various export formats for citations."""
|
|
|
|
def test_ris_export_with_multiple_citations(self):
|
|
"""Test RIS export handles multiple citations to same source."""
|
|
content = """# Research Report
|
|
|
|
Multiple citations [1] to same [1] source [1]. And another [2].
|
|
|
|
## Sources
|
|
|
|
[1] Machine Learning Paper
|
|
URL: https://arxiv.org/abs/2023.12345
|
|
|
|
[2] Nature Article
|
|
URL: https://nature.com/articles/s41586-023-06789-9"""
|
|
|
|
exporter = RISExporter()
|
|
ris_output = exporter.export_to_ris(content)
|
|
|
|
# Should have exactly 2 RIS entries (not 3)
|
|
assert ris_output.count("TY - ELEC") == 2
|
|
assert ris_output.count("ER -") == 2
|
|
|
|
# Should include both URLs
|
|
assert "https://arxiv.org/abs/2023.12345" in ris_output
|
|
assert "https://nature.com/articles/s41586-023-06789-9" in ris_output
|
|
|
|
def test_ris_export_with_special_characters(self):
|
|
"""Test RIS export handles special characters properly."""
|
|
content = """# Research Report
|
|
|
|
Citation [1] with special chars.
|
|
|
|
## Sources
|
|
|
|
[1] Paper with Special Chars: Ñoño, Müller & 中文
|
|
URL: https://example.com/special-chars?param=value&other=test"""
|
|
|
|
exporter = RISExporter()
|
|
ris_output = exporter.export_to_ris(content)
|
|
|
|
# Should handle special characters
|
|
assert "Ñoño" in ris_output
|
|
assert "Müller" in ris_output
|
|
assert "中文" in ris_output
|
|
assert "param=value&other=test" in ris_output
|
|
|
|
def test_latex_export_with_equations(self):
|
|
"""Test LaTeX export with equations in content."""
|
|
content = r"""# Research Report
|
|
|
|
The equation $E = mc^2$ from [1] shows mass-energy equivalence.
|
|
|
|
Consider the integral $$\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}$$ from [2].
|
|
|
|
## Sources
|
|
|
|
[1] Einstein's Original Paper
|
|
URL: https://physics.org/einstein-1905
|
|
|
|
[2] Mathematical Methods
|
|
URL: https://math.edu/integrals"""
|
|
|
|
exporter = LaTeXExporter()
|
|
latex_output = exporter.export_to_latex(content)
|
|
|
|
# Should preserve math mode
|
|
assert r"$E = mc^2$" in latex_output
|
|
assert r"$$\int_0^\infty" in latex_output
|
|
|
|
# Should have proper bibliography
|
|
assert r"\bibitem{1}" in latex_output
|
|
assert r"\bibitem{2}" in latex_output
|
|
|
|
def test_latex_export_special_characters_escaping(self):
|
|
"""Test LaTeX export properly escapes special characters."""
|
|
content = """# Research Report
|
|
|
|
Citation [1] with special LaTeX chars: $, %, &, #, _, {, }.
|
|
|
|
## Sources
|
|
|
|
[1] Paper & Title with % Special # Chars
|
|
URL: https://example.com/paper_with_underscore"""
|
|
|
|
exporter = LaTeXExporter()
|
|
latex_output = exporter.export_to_latex(content)
|
|
|
|
# Should escape special LaTeX characters in text
|
|
assert r"\%" in latex_output
|
|
assert r"\&" in latex_output
|
|
assert r"\#" in latex_output
|
|
assert (
|
|
r"\_" in latex_output or "_" in latex_output
|
|
) # URLs might not be escaped
|
|
|
|
def test_quarto_export_with_metadata(self):
|
|
"""Test Quarto export includes proper metadata."""
|
|
content = """# Deep Learning Research Report
|
|
|
|
This reviews [1] and extends [2].
|
|
|
|
## Sources
|
|
|
|
[1] Attention Is All You Need
|
|
URL: https://arxiv.org/abs/1706.03762
|
|
|
|
[2] BERT: Pre-training of Deep Bidirectional Transformers
|
|
URL: https://arxiv.org/abs/1810.04805"""
|
|
|
|
exporter = QuartoExporter()
|
|
quarto_output = exporter.export_to_quarto(content)
|
|
|
|
# Should have YAML frontmatter
|
|
assert "---" in quarto_output
|
|
assert "title:" in quarto_output
|
|
assert "bibliography:" in quarto_output
|
|
|
|
# Should extract title
|
|
assert "Deep Learning Research Report" in quarto_output
|
|
|
|
# Should have citations in Quarto format
|
|
assert "[@ref1]" in quarto_output or "@ref1" in quarto_output
|
|
assert "[@ref2]" in quarto_output or "@ref2" in quarto_output
|
|
|
|
def test_export_with_missing_sources_section(self):
|
|
"""Test export formats handle missing sources section gracefully."""
|
|
content = """# Research Report
|
|
|
|
This has citations [1] and [2] but no sources section."""
|
|
|
|
# Test RIS
|
|
ris_exporter = RISExporter()
|
|
ris_output = ris_exporter.export_to_ris(content)
|
|
assert ris_output == "" # Should return empty for no sources
|
|
|
|
# Test LaTeX
|
|
latex_exporter = LaTeXExporter()
|
|
latex_output = latex_exporter.export_to_latex(content)
|
|
assert "bibliography" not in latex_output.lower()
|
|
|
|
# Test Quarto
|
|
quarto_exporter = QuartoExporter()
|
|
quarto_output = quarto_exporter.export_to_quarto(content)
|
|
# Should still have content but no bibliography
|
|
assert "This has citations" in quarto_output
|
|
|
|
def test_export_with_duplicate_urls(self):
|
|
"""Test export formats handle duplicate URLs correctly."""
|
|
content = """# Report
|
|
|
|
Cite [1] and [2].
|
|
|
|
## Sources
|
|
|
|
[1] First Title
|
|
URL: https://same.url/paper
|
|
|
|
[2] Different Title Same URL
|
|
URL: https://same.url/paper"""
|
|
|
|
# RIS should have separate entries
|
|
ris_exporter = RISExporter()
|
|
ris_output = ris_exporter.export_to_ris(content)
|
|
assert ris_output.count("TY - ELEC") == 2
|
|
# The fixed exporter emits clean RIS records only (no echo of the
|
|
# raw source text), so the shared URL appears once per entry → 2.
|
|
assert ris_output.count("https://same.url/paper") == 2
|
|
|
|
# LaTeX should have separate bibitems
|
|
latex_exporter = LaTeXExporter()
|
|
latex_output = latex_exporter.export_to_latex(content)
|
|
assert r"\bibitem{1}" in latex_output
|
|
assert r"\bibitem{2}" in latex_output
|
|
|
|
def test_export_preserves_markdown_structure(self):
|
|
"""Test that exports preserve document structure."""
|
|
content = """# Main Title
|
|
|
|
## Introduction
|
|
|
|
This is **bold** and *italic* text [1].
|
|
|
|
### Subsection
|
|
|
|
- List item [2]
|
|
- Another item
|
|
- Nested [3]
|
|
|
|
## Sources
|
|
|
|
[1] Source One
|
|
URL: https://one.com
|
|
|
|
[2] Source Two
|
|
URL: https://two.com
|
|
|
|
[3] Source Three
|
|
URL: https://three.com"""
|
|
|
|
# Test LaTeX preserves structure
|
|
latex_exporter = LaTeXExporter()
|
|
latex_output = latex_exporter.export_to_latex(content)
|
|
assert r"\section" in latex_output
|
|
assert r"\subsection" in latex_output
|
|
assert r"\textbf{bold}" in latex_output
|
|
assert r"\textit{italic}" in latex_output
|
|
assert r"\begin{itemize}" in latex_output
|
|
|
|
def test_quarto_bibliography_generation(self):
|
|
"""Test Quarto generates proper bibliography entries."""
|
|
content = """# Research
|
|
|
|
Citation [1].
|
|
|
|
## Sources
|
|
|
|
[1] 2023 AI Paper by Smith, J. and Doe, J.
|
|
URL: https://ai-journal.com/2023/paper"""
|
|
|
|
exporter = QuartoExporter()
|
|
quarto_output = exporter.export_to_quarto(content)
|
|
|
|
# Should generate a references section
|
|
assert "references:" in quarto_output.lower() or "@" in quarto_output
|
|
|
|
def test_export_with_malformed_content(self):
|
|
"""Test exports handle malformed content gracefully."""
|
|
content = """# Report
|
|
|
|
[1] [2 [3]] [[4]
|
|
|
|
## Sources
|
|
|
|
[1 Source One
|
|
URL: https://one.com
|
|
|
|
[2] Source Two
|
|
No URL
|
|
|
|
[3]
|
|
URL: https://three.com"""
|
|
|
|
# All exporters should handle this without crashing
|
|
exporters = [RISExporter(), LaTeXExporter(), QuartoExporter()]
|
|
|
|
for exporter in exporters:
|
|
try:
|
|
if isinstance(exporter, RISExporter):
|
|
output = exporter.export_to_ris(content)
|
|
elif isinstance(exporter, LaTeXExporter):
|
|
output = exporter.export_to_latex(content)
|
|
elif isinstance(exporter, QuartoExporter):
|
|
output = exporter.export_to_quarto(content)
|
|
assert output is not None
|
|
except Exception as e:
|
|
pytest.fail(
|
|
f"{exporter.__class__.__name__} failed on malformed content: {e}"
|
|
)
|
|
|
|
def test_ris_export_field_extraction(self):
|
|
"""Test RIS export extracts various metadata fields."""
|
|
content = """# Report
|
|
|
|
Citation [1].
|
|
|
|
## Sources
|
|
|
|
[1] Deep Learning in Nature 2023 by LeCun, Y., Bengio, Y., & Hinton, G.
|
|
Published in Nature Machine Intelligence, Volume 5, Pages 123-145
|
|
DOI: 10.1038/s42256-023-00001-2
|
|
URL: https://nature.com/articles/s42256-023-00001-2"""
|
|
|
|
exporter = RISExporter()
|
|
ris_output = exporter.export_to_ris(content)
|
|
|
|
# Should extract various fields
|
|
assert "TI - Deep Learning in Nature 2023" in ris_output
|
|
assert "AU - LeCun, Y." in ris_output or "LeCun" in ris_output
|
|
assert "PY - 2023" in ris_output
|
|
assert "DO - 10.1038/s42256-023-00001-2" in ris_output
|
|
|
|
def test_concurrent_export_formats(self):
|
|
"""Test that different export formats can be used concurrently."""
|
|
content = """# Research
|
|
|
|
Multiple [1] citations [1] here [2].
|
|
|
|
## Sources
|
|
|
|
[1] First Paper
|
|
URL: https://first.com
|
|
|
|
[2] Second Paper
|
|
URL: https://second.com"""
|
|
|
|
# Format for different uses
|
|
formatter_domain = CitationFormatter(
|
|
mode=CitationMode.DOMAIN_HYPERLINKS
|
|
)
|
|
formatted_domain = formatter_domain.format_document(content)
|
|
|
|
formatter_number = CitationFormatter(
|
|
mode=CitationMode.NUMBER_HYPERLINKS
|
|
)
|
|
formatted_number = formatter_number.format_document(content)
|
|
|
|
# Export to different formats
|
|
ris_output = RISExporter().export_to_ris(content)
|
|
latex_output = LaTeXExporter().export_to_latex(content)
|
|
quarto_output = QuartoExporter().export_to_quarto(content)
|
|
|
|
# All should work independently
|
|
assert "[first.com]" in formatted_domain
|
|
assert "[[1]](https://first.com)" in formatted_number
|
|
assert "TY - ELEC" in ris_output
|
|
assert r"\bibitem" in latex_output
|
|
assert "bibliography" in quarto_output or "@" in quarto_output
|