"""High-value pure logic tests for citation_formatter.py.""" import pytest from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, CitationMode, LaTeXExporter, QuartoExporter, RISExporter, find_sources_section, ) # --------------------------------------------------------------------------- # CitationMode enum # --------------------------------------------------------------------------- class TestCitationMode: def test_enum_values(self): assert CitationMode.NUMBER_HYPERLINKS.value == "number_hyperlinks" assert CitationMode.DOMAIN_HYPERLINKS.value == "domain_hyperlinks" assert CitationMode.DOMAIN_ID_HYPERLINKS.value == "domain_id_hyperlinks" assert ( CitationMode.DOMAIN_ID_ALWAYS_HYPERLINKS.value == "domain_id_always_hyperlinks" ) assert ( CitationMode.SOURCE_TAGGED_HYPERLINKS.value == "source_tagged_hyperlinks" ) assert CitationMode.NO_HYPERLINKS.value == "no_hyperlinks" def test_enum_member_count(self): # NUMBER_HYPERLINKS, DOMAIN_HYPERLINKS, DOMAIN_ID_HYPERLINKS, # DOMAIN_ID_ALWAYS_HYPERLINKS, SOURCE_TAGGED_HYPERLINKS, NO_HYPERLINKS assert len(CitationMode) == 6 # --------------------------------------------------------------------------- # find_sources_section # --------------------------------------------------------------------------- class TestFindSourcesSection: def test_markdown_heading_sources(self): text = "Some text\n## Sources\n[1] Foo" assert find_sources_section(text) == text.index("## Sources") def test_markdown_heading_references(self): text = "Body\n# References\n[1] Bar" assert find_sources_section(text) == text.index("# References") def test_plain_label_sources(self): text = "Body paragraph\nSources:\n[1] Item" assert find_sources_section(text) == text.index("Sources:") def test_case_insensitive(self): text = "Body\n## BIBLIOGRAPHY\n[1] X" assert find_sources_section(text) != -1 def test_no_section_returns_negative_one(self): assert find_sources_section("No references here.") == -1 def test_empty_string(self): assert find_sources_section("") == -1 def test_citations_heading_detected(self): text = "Content\n### Citations\nSome refs" assert find_sources_section(text) == text.index("### Citations") # --------------------------------------------------------------------------- # CitationFormatter._extract_domain # --------------------------------------------------------------------------- class TestExtractDomain: def setup_method(self): self.fmt = CitationFormatter() def test_known_domain_arxiv(self): assert ( self.fmt._extract_domain("https://arxiv.org/abs/1234") == "arxiv.org" ) def test_known_domain_github(self): assert ( self.fmt._extract_domain("https://github.com/user/repo") == "github.com" ) def test_www_prefix_stripped(self): assert ( self.fmt._extract_domain("https://www.example.com/page") == "example.com" ) def test_subdomain_returns_main_domain(self): assert ( self.fmt._extract_domain("https://docs.python.org/3/") == "python.org" ) def test_empty_string_returns_empty(self): # urlparse("") produces empty netloc; no exception is raised assert self.fmt._extract_domain("") == "" def test_no_scheme_url_returns_empty(self): # urlparse without scheme puts everything in path, netloc is empty assert self.fmt._extract_domain("not-a-url") == "" def test_none_url_raises(self): # None triggers TypeError which is not caught by the handler with pytest.raises(TypeError): self.fmt._extract_domain(None) # --------------------------------------------------------------------------- # CitationFormatter._parse_sources # --------------------------------------------------------------------------- class TestParseSources: def setup_method(self): self.fmt = CitationFormatter() def test_basic_source_with_url(self): text = "[1] My Title\n URL: https://example.com" result = self.fmt._parse_sources(text) assert "1" in result assert result["1"] == ("My Title", "https://example.com") def test_source_without_url(self): text = "[2] Title Only" result = self.fmt._parse_sources(text) assert "2" in result assert result["2"][1] == "" def test_multiple_sources(self): text = "[1] First\n[2] Second" result = self.fmt._parse_sources(text) assert len(result) == 2 def test_empty_string(self): result = self.fmt._parse_sources("") assert result == {} def test_comma_separated_citation_numbers(self): text = "[3, 4] Shared Title\n URL: https://shared.com" result = self.fmt._parse_sources(text) assert "3" in result assert "4" in result assert result["3"][0] == "Shared Title" # --------------------------------------------------------------------------- # CitationFormatter._replace_comma_citations # --------------------------------------------------------------------------- class TestReplaceCommaCitations: def setup_method(self): self.fmt = CitationFormatter() def test_comma_separated_replaced(self): lookup = {"1": ("T1", "http://a.com"), "2": ("T2", "http://b.com")} def format_one(num, data): _, url = data return f"[[{num}]]({url})" result = self.fmt._replace_comma_citations( "See [1, 2] here", lookup, format_one ) assert "[[1]](http://a.com)" in result assert "[[2]](http://b.com)" in result assert "[1, 2]" not in result def test_missing_citation_falls_back(self): lookup = {"1": ("T1", "http://a.com")} def format_one(num, data): return f"[{num}]" result = self.fmt._replace_comma_citations( "See [1, 99] here", lookup, format_one ) assert "[99]" in result # --------------------------------------------------------------------------- # CitationFormatter._format_number_hyperlinks # --------------------------------------------------------------------------- class TestFormatNumberHyperlinks: def setup_method(self): self.fmt = CitationFormatter() def test_basic_hyperlink(self): sources = {"1": ("Title", "https://example.com")} result = self.fmt._format_number_hyperlinks( "See [1] for details.", sources ) assert "[[1]](https://example.com)" in result def test_source_without_url_unchanged(self): sources = {"1": ("Title", "")} result = self.fmt._format_number_hyperlinks( "See [1] for details.", sources ) assert "[1]" in result assert "]()" not in result def test_source_word_pattern(self): sources = {"1": ("Title", "https://example.com")} result = self.fmt._format_number_hyperlinks( "According to Source 1.", sources ) assert "[[1]](https://example.com)" in result # --------------------------------------------------------------------------- # CitationFormatter._format_domain_id_hyperlinks # --------------------------------------------------------------------------- class TestFormatDomainIdHyperlinks: def setup_method(self): self.fmt = CitationFormatter() def test_single_domain_no_id_suffix(self): sources = {"1": ("Title", "https://arxiv.org/abs/123")} result = self.fmt._format_domain_id_hyperlinks("See [1].", sources) assert "[[arxiv.org]]" in result def test_multiple_same_domain_gets_id(self): sources = { "1": ("A", "https://arxiv.org/abs/1"), "2": ("B", "https://arxiv.org/abs/2"), } result = self.fmt._format_domain_id_hyperlinks( "See [1] and [2].", sources ) assert "arxiv.org-1" in result assert "arxiv.org-2" in result # --------------------------------------------------------------------------- # LaTeXExporter._escape_latex # --------------------------------------------------------------------------- class TestLatexEscapeLatex: def setup_method(self): self.exporter = LaTeXExporter() def test_ampersand(self): assert self.exporter._escape_latex("A & B") == r"A \& B" def test_percent(self): assert self.exporter._escape_latex("100%") == r"100\%" def test_underscore(self): assert self.exporter._escape_latex("my_var") == r"my\_var" def test_hash(self): assert self.exporter._escape_latex("#1") == r"\#1" def test_tilde(self): assert r"\textasciitilde{}" in self.exporter._escape_latex("~") def test_empty_string(self): assert self.exporter._escape_latex("") == "" def test_multiple_specials(self): result = self.exporter._escape_latex("A & B % C") assert r"\&" in result assert r"\%" in result # --------------------------------------------------------------------------- # LaTeXExporter._convert_lists # --------------------------------------------------------------------------- class TestLatexConvertLists: def setup_method(self): self.exporter = LaTeXExporter() def test_single_bullet(self): result = self.exporter._convert_lists("- Hello") assert r"\begin{itemize}" in result assert r"\item Hello" in result assert r"\end{itemize}" in result def test_multiple_bullets(self): result = self.exporter._convert_lists("- One\n- Two\n- Three") assert result.count(r"\item") == 3 # Only one begin/end pair assert result.count(r"\begin{itemize}") == 1 assert result.count(r"\end{itemize}") == 1 def test_no_bullets_unchanged(self): text = "Just a paragraph." result = self.exporter._convert_lists(text) assert r"\begin{itemize}" not in result def test_list_ends_on_non_list_line(self): result = self.exporter._convert_lists("- Item\nParagraph text") assert r"\end{itemize}" in result # --------------------------------------------------------------------------- # RISExporter basic entry formatting # --------------------------------------------------------------------------- class TestRISExporter: def setup_method(self): self.exporter = RISExporter() def test_no_sources_section_returns_empty(self): result = self.exporter.export_to_ris("No references here at all.") assert result == "" def test_basic_export_contains_ris_markers(self): content = ( "Body text\n## Sources\n[1] My Paper\n URL: https://example.com" ) result = self.exporter.export_to_ris(content) assert "TY - ELEC" in result assert "ER - " in result assert "ID - ref1" in result def test_url_included_in_entry(self): content = "Text\n## Sources\n[1] Paper Title\n URL: https://example.com/paper" result = self.exporter.export_to_ris(content) assert "UR - https://example.com/paper" in result # --------------------------------------------------------------------------- # QuartoExporter output # --------------------------------------------------------------------------- class TestQuartoExporter: def setup_method(self): self.exporter = QuartoExporter() def test_yaml_header_present(self): content = "# My Report\nSome text [1]\n## Sources\n[1] Ref" result = self.exporter.export_to_quarto(content) assert result.startswith("---") assert "title:" in result assert "bibliography: references.bib" in result def test_citation_converted_to_quarto_format(self): content = "See [1] for details.\n## Sources\n[1] A paper" result = self.exporter.export_to_quarto(content) assert "[@ref1]" in result def test_comma_citations_converted(self): content = "See [1, 2] here.\n## Sources\n[1] A\n[2] B" result = self.exporter.export_to_quarto(content) assert "@ref1" in result assert "@ref2" in result def test_custom_title(self): content = "Some body text" result = self.exporter.export_to_quarto(content, title="Custom Title") assert "Custom Title" in result def test_title_extracted_from_heading(self): content = "# Extracted Title\nBody" result = self.exporter.export_to_quarto(content) assert "Extracted Title" in result