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

285 lines
9.8 KiB
Python

"""
Citation regression tests covering:
- Parametrized test across all 5 CitationModes
- Multi-source DOMAIN_ID_HYPERLINKS with repeated domains
- Export round-trip (Quarto BibTeX consistency)
- LaTeX math-mode edge cases
- RIS author extraction false positives
- Quarto hardcoded year=2024
Source: src/local_deep_research/text_optimization/citation_formatter.py
"""
import re
import pytest
from local_deep_research.text_optimization.citation_formatter import (
CitationFormatter,
CitationMode,
QuartoExporter,
RISExporter,
LaTeXExporter,
)
# ---------------------------------------------------------------------------
# Shared test content
# ---------------------------------------------------------------------------
SAMPLE_CONTENT = """# Research Report
This is about AI [1] and ML [2].
## Sources
[1] Introduction to Artificial Intelligence
URL: https://arxiv.org/abs/2024.12345
[2] Machine Learning Fundamentals
URL: https://github.com/ml-project/fundamentals
"""
MULTI_DOMAIN_CONTENT = """# Report
Topic A [1] and topic B [2] with extra [3].
## Sources
[1] First arxiv paper
URL: https://arxiv.org/abs/2024.001
[2] Second arxiv paper
URL: https://arxiv.org/abs/2024.002
[3] GitHub project
URL: https://github.com/project/repo
"""
# ---------------------------------------------------------------------------
# Parametrized test across all 5 modes
# ---------------------------------------------------------------------------
class TestAllCitationModes:
"""Same document formatted across all 5 CitationModes."""
@pytest.mark.parametrize(
"mode,expected_fragment",
[
(CitationMode.NUMBER_HYPERLINKS, "[[1]](https://arxiv.org"),
(CitationMode.DOMAIN_HYPERLINKS, "[[arxiv.org]](https://arxiv.org"),
(CitationMode.DOMAIN_ID_HYPERLINKS, "arxiv.org"),
(CitationMode.DOMAIN_ID_ALWAYS_HYPERLINKS, "arxiv.org-1"),
(CitationMode.NO_HYPERLINKS, "[1]"),
],
)
def test_mode_produces_expected_fragment(self, mode, expected_fragment):
"""Each mode produces its characteristic citation fragment."""
formatter = CitationFormatter(mode)
result = formatter.format_document(SAMPLE_CONTENT)
assert expected_fragment in result
def test_no_hyperlinks_returns_content_unchanged(self):
"""NO_HYPERLINKS mode returns content unchanged."""
formatter = CitationFormatter(CitationMode.NO_HYPERLINKS)
result = formatter.format_document(SAMPLE_CONTENT)
assert result == SAMPLE_CONTENT
# ---------------------------------------------------------------------------
# Multi-source DOMAIN_ID_HYPERLINKS
# ---------------------------------------------------------------------------
class TestDomainIdMultiSource:
"""DOMAIN_ID_HYPERLINKS adds IDs only for repeated domains."""
def test_repeated_domain_gets_ids(self):
"""Two arxiv sources get arxiv.org-1 and arxiv.org-2."""
formatter = CitationFormatter(CitationMode.DOMAIN_ID_HYPERLINKS)
result = formatter.format_document(MULTI_DOMAIN_CONTENT)
assert "arxiv.org-1" in result or "arxiv.org-2" in result
def test_unique_domain_no_id(self):
"""Single github source gets just github.com (no -1)."""
formatter = CitationFormatter(CitationMode.DOMAIN_ID_HYPERLINKS)
result = formatter.format_document(MULTI_DOMAIN_CONTENT)
# github.com should appear without -1 suffix
assert "[[github.com]]" in result
# ---------------------------------------------------------------------------
# Quarto export
# ---------------------------------------------------------------------------
class TestQuartoExport:
"""Quarto export round-trip and consistency."""
def test_all_refs_have_bibtex_entries(self):
"""Every @refN in Quarto body has matching BibTeX @misc{refN,...}."""
exporter = QuartoExporter()
result = exporter.export_to_quarto(SAMPLE_CONTENT, "Test Report")
# Find all @refN references in body
ref_pattern = re.compile(r"@ref(\d+)")
refs_in_body = set(ref_pattern.findall(result))
# Find all BibTeX entries
bib_pattern = re.compile(r"@misc\{ref(\d+),")
bib_entries = set(bib_pattern.findall(result))
assert refs_in_body, "Should have at least one @ref in body"
assert refs_in_body.issubset(bib_entries), (
f"Missing BibTeX entries for: {refs_in_body - bib_entries}"
)
def test_bibtex_keys_unique(self):
"""All BibTeX keys are unique."""
exporter = QuartoExporter()
result = exporter.export_to_quarto(SAMPLE_CONTENT)
bib_pattern = re.compile(r"@misc\{(ref\d+),")
keys = bib_pattern.findall(result)
assert len(keys) == len(set(keys)), f"Duplicate BibTeX keys: {keys}"
def test_hardcoded_year_2024(self):
"""Documents the hardcoded year = {2024} at line 543."""
exporter = QuartoExporter()
result = exporter.export_to_quarto(SAMPLE_CONTENT)
# Every BibTeX entry should have year = {2024}
assert "year = {2024}" in result
def test_yaml_header_included(self):
"""Quarto output includes YAML front matter."""
exporter = QuartoExporter()
result = exporter.export_to_quarto(SAMPLE_CONTENT, "My Report")
assert result.startswith("---")
assert 'title: "My Report"' in result
def test_comma_citations_converted(self):
"""Comma-separated citations [1, 2] converted to [@ref1, @ref2]."""
content = "Text [1, 2] here.\n\n## Sources\n\n[1] A\nURL: https://a.com\n\n[2] B\nURL: https://b.com\n"
exporter = QuartoExporter()
result = exporter.export_to_quarto(content)
assert "@ref1" in result
assert "@ref2" in result
# ---------------------------------------------------------------------------
# RIS export
# ---------------------------------------------------------------------------
class TestRISExport:
"""RIS format export tests."""
def test_basic_ris_structure(self):
"""RIS output has TY, TI, UR, ER tags."""
exporter = RISExporter()
result = exporter.export_to_ris(SAMPLE_CONTENT)
assert "TY - ELEC" in result
assert "TI -" in result
assert "ER - " in result
def test_url_included_in_ris(self):
"""URL from source is included in RIS entry."""
exporter = RISExporter()
result = exporter.export_to_ris(SAMPLE_CONTENT)
assert "UR - https://arxiv.org" in result
def test_author_extraction_by_keyword(self):
"""'by Author Name' extracts author correctly."""
content = """# Report
Text [1].
## Sources
[1] Research Paper by John Smith and Jane Doe
URL: https://example.com
"""
exporter = RISExporter()
result = exporter.export_to_ris(content)
assert "AU -" in result
def test_author_false_positive_stand_by_me(self):
"""'Stand by Me' should NOT extract 'Me' as an author.
The regex \\bby\\s+([^.\\n]+?) matches 'by Me' in 'Stand by Me'.
This documents the false positive behavior of the current regex.
"""
content = """# Report
Text [1].
## Sources
[1] Stand by Me - A Classic Film Analysis
URL: https://example.com
"""
exporter = RISExporter()
result = exporter.export_to_ris(content)
# The regex WILL match "by Me" — this documents the current behavior
# The "AU - Me" line documents this false positive
if "AU -" in result:
# If authors are extracted, "Me" would be one of them
# This is a known false positive from the simple regex
pass # Document behavior without asserting it's correct
def test_no_sources_section_returns_empty(self):
"""Content without sources section returns empty string."""
exporter = RISExporter()
result = exporter.export_to_ris("Just some text without sources")
assert result == ""
def test_arxiv_publisher(self):
"""arxiv.org URL produces PB - arXiv."""
exporter = RISExporter()
result = exporter.export_to_ris(SAMPLE_CONTENT)
assert "PB - arXiv" in result
def test_github_publisher(self):
"""github.com URL produces PB - GitHub."""
exporter = RISExporter()
result = exporter.export_to_ris(SAMPLE_CONTENT)
assert "PB - GitHub" in result
# ---------------------------------------------------------------------------
# LaTeX export
# ---------------------------------------------------------------------------
class TestLaTeXExport:
"""LaTeX export edge cases."""
def test_basic_latex_structure(self):
"""LaTeX output includes documentclass and begin{document}."""
exporter = LaTeXExporter()
result = exporter.export_to_latex(SAMPLE_CONTENT)
assert "\\documentclass" in result
assert "\\begin{document}" in result
assert "\\end{document}" in result
def test_headings_converted(self):
"""Markdown headings converted to LaTeX sections."""
exporter = LaTeXExporter()
result = exporter.export_to_latex("# Title\n\n## Subtitle\n\nText")
assert "\\section{" in result
assert "\\subsection{" in result
def test_unbalanced_dollar_sign(self):
"""Unbalanced $ (e.g., 'costs $100') in content.
LaTeX exporter should handle or at least not crash on unbalanced $.
"""
content = "# Report\n\nThis costs $100 per unit.\n\n## Sources\n\n[1] Test\nURL: https://a.com\n"
exporter = LaTeXExporter()
# Should not raise
result = exporter.export_to_latex(content)
assert isinstance(result, str)
def test_citations_converted_to_cite(self):
"""[1] converted to \\cite{ref1} or similar."""
exporter = LaTeXExporter()
result = exporter.export_to_latex(SAMPLE_CONTENT)
assert "\\cite{" in result or "ref1" in result