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
168 lines
6.0 KiB
Python
168 lines
6.0 KiB
Python
"""Edge-case tests for citation_formatter — idempotency, parsing, and export boundaries."""
|
|
|
|
|
|
def _make_document(body, sources):
|
|
"""Helper: combine body text with a sources section."""
|
|
return body + "\n\n## Sources\n" + sources
|
|
|
|
|
|
class TestAlreadyFormattedCitations:
|
|
"""Ensure already-formatted citations are not double-processed."""
|
|
|
|
def test_already_formatted_not_rematched(self):
|
|
"""[[1]](url) should NOT be re-formatted to [[[1]]](url)."""
|
|
from local_deep_research.text_optimization.citation_formatter import (
|
|
CitationFormatter,
|
|
CitationMode,
|
|
)
|
|
|
|
formatter = CitationFormatter(CitationMode.NUMBER_HYPERLINKS)
|
|
doc = _make_document(
|
|
"See [[1]](https://example.com) for details.",
|
|
"[1] Example Source\n URL: https://example.com",
|
|
)
|
|
result = formatter.format_document(doc)
|
|
# The negative lookbehind (?<![\[) should prevent matching inside [[ ]]
|
|
assert "[[[1]]]" not in result
|
|
|
|
def test_format_document_idempotent(self):
|
|
"""Calling format_document twice produces identical output."""
|
|
from local_deep_research.text_optimization.citation_formatter import (
|
|
CitationFormatter,
|
|
CitationMode,
|
|
)
|
|
|
|
formatter = CitationFormatter(CitationMode.NUMBER_HYPERLINKS)
|
|
doc = _make_document(
|
|
"See [1] for details.",
|
|
"[1] Example Source\n URL: https://example.com",
|
|
)
|
|
first = formatter.format_document(doc)
|
|
second = formatter.format_document(first)
|
|
assert first == second
|
|
|
|
|
|
class TestSourceParsing:
|
|
"""Tests for _parse_sources edge cases."""
|
|
|
|
def test_parse_sources_tab_indented_url(self):
|
|
"""Tab indentation in \\tURL: should be parsed."""
|
|
from local_deep_research.text_optimization.citation_formatter import (
|
|
CitationFormatter,
|
|
CitationMode,
|
|
)
|
|
|
|
formatter = CitationFormatter(CitationMode.NUMBER_HYPERLINKS)
|
|
sources_text = "## Sources\n[1] Title here\n\tURL: https://example.com"
|
|
sources = formatter._parse_sources(sources_text)
|
|
# The regex uses \\s* which matches tabs
|
|
assert "1" in sources
|
|
_, url = sources["1"]
|
|
assert url == "https://example.com"
|
|
|
|
|
|
class TestDomainIdAssignment:
|
|
"""Tests for domain ID assignment order."""
|
|
|
|
def test_domain_id_assignment_preserves_source_order(self):
|
|
"""Source 1 gets -1, source 5 gets -2 deterministically for same domain."""
|
|
from local_deep_research.text_optimization.citation_formatter import (
|
|
CitationFormatter,
|
|
CitationMode,
|
|
)
|
|
|
|
formatter = CitationFormatter(CitationMode.DOMAIN_ID_ALWAYS_HYPERLINKS)
|
|
doc = _make_document(
|
|
"See [1] and [5].",
|
|
"[1] First Article\n URL: https://arxiv.org/abs/1234\n"
|
|
"[5] Second Article\n URL: https://arxiv.org/abs/5678",
|
|
)
|
|
result = formatter.format_document(doc)
|
|
# Both are from arxiv.org, should get -1 and -2
|
|
assert "arxiv.org-1" in result
|
|
assert "arxiv.org-2" in result
|
|
|
|
|
|
class TestRISExporterBoundary:
|
|
"""Tests for RIS exporter deduplication boundary."""
|
|
|
|
def test_ris_exporter_stops_at_all_sources(self):
|
|
"""Deduplication boundary at '## ALL SOURCES' section."""
|
|
from local_deep_research.text_optimization.citation_formatter import (
|
|
RISExporter,
|
|
)
|
|
|
|
exporter = RISExporter()
|
|
content = (
|
|
"## Sources\n"
|
|
"[1] First Source\n URL: https://example.com\n\n"
|
|
"## ALL SOURCES\n"
|
|
"[1] First Source (duplicate)\n URL: https://example.com\n"
|
|
"[2] Second Source\n URL: https://other.com"
|
|
)
|
|
ris = exporter.export_to_ris(content)
|
|
# Should only include source [1] from before the ALL SOURCES marker
|
|
assert "ref1" in ris
|
|
assert "ref2" not in ris
|
|
|
|
|
|
class TestLaTeXListBalance:
|
|
"""Tests for LaTeX list conversion."""
|
|
|
|
def test_latex_list_balanced_with_empty_lines(self):
|
|
"""\\begin{itemize} and \\end{itemize} are always balanced."""
|
|
from local_deep_research.text_optimization.citation_formatter import (
|
|
LaTeXExporter,
|
|
)
|
|
|
|
exporter = LaTeXExporter()
|
|
content = (
|
|
"# Title\n\n- Item one\n- Item two\n\nSome text\n\n- Item three\n"
|
|
)
|
|
result = exporter.export_to_latex(content)
|
|
begin_count = result.count("\\begin{itemize}")
|
|
end_count = result.count("\\end{itemize}")
|
|
assert begin_count == end_count
|
|
assert begin_count >= 1
|
|
|
|
|
|
class TestSourceWordPattern:
|
|
"""Tests for 'Source X' pattern matching."""
|
|
|
|
def test_source_word_with_trailing_period(self):
|
|
"""'Source 1.' at end-of-sentence converts correctly."""
|
|
from local_deep_research.text_optimization.citation_formatter import (
|
|
CitationFormatter,
|
|
CitationMode,
|
|
)
|
|
|
|
formatter = CitationFormatter(CitationMode.NUMBER_HYPERLINKS)
|
|
doc = _make_document(
|
|
"According to Source 1.",
|
|
"[1] Example\n URL: https://example.com",
|
|
)
|
|
result = formatter.format_document(doc)
|
|
# "Source 1" should be replaced; the period remains
|
|
assert "Source 1" not in result.split("## Sources")[0]
|
|
|
|
def test_comma_citations_with_spaces(self):
|
|
"""[1, 2, 3] with varying space patterns."""
|
|
from local_deep_research.text_optimization.citation_formatter import (
|
|
CitationFormatter,
|
|
CitationMode,
|
|
)
|
|
|
|
formatter = CitationFormatter(CitationMode.NUMBER_HYPERLINKS)
|
|
doc = _make_document(
|
|
"See [1, 2, 3] for details.",
|
|
"[1] Source A\n URL: https://a.com\n"
|
|
"[2] Source B\n URL: https://b.com\n"
|
|
"[3] Source C\n URL: https://c.com",
|
|
)
|
|
result = formatter.format_document(doc)
|
|
body = result.split("## Sources")[0]
|
|
# Each citation should be individually formatted
|
|
assert "[[1]]" in body
|
|
assert "[[2]]" in body
|
|
assert "[[3]]" in body
|