""" Behavioral tests for citation_formatter module. Tests CitationFormatter, QuartoExporter, RISExporter, and LaTeXExporter classes. """ class TestCitationModeEnum: """Tests for CitationMode enum.""" def test_number_hyperlinks_mode_exists(self): """NUMBER_HYPERLINKS mode exists.""" from local_deep_research.text_optimization.citation_formatter import ( CitationMode, ) assert hasattr(CitationMode, "NUMBER_HYPERLINKS") def test_domain_hyperlinks_mode_exists(self): """DOMAIN_HYPERLINKS mode exists.""" from local_deep_research.text_optimization.citation_formatter import ( CitationMode, ) assert hasattr(CitationMode, "DOMAIN_HYPERLINKS") def test_domain_id_hyperlinks_mode_exists(self): """DOMAIN_ID_HYPERLINKS mode exists.""" from local_deep_research.text_optimization.citation_formatter import ( CitationMode, ) assert hasattr(CitationMode, "DOMAIN_ID_HYPERLINKS") def test_domain_id_always_hyperlinks_mode_exists(self): """DOMAIN_ID_ALWAYS_HYPERLINKS mode exists.""" from local_deep_research.text_optimization.citation_formatter import ( CitationMode, ) assert hasattr(CitationMode, "DOMAIN_ID_ALWAYS_HYPERLINKS") def test_no_hyperlinks_mode_exists(self): """NO_HYPERLINKS mode exists.""" from local_deep_research.text_optimization.citation_formatter import ( CitationMode, ) assert hasattr(CitationMode, "NO_HYPERLINKS") def test_mode_values_are_strings(self): """All mode values are strings.""" from local_deep_research.text_optimization.citation_formatter import ( CitationMode, ) for mode in CitationMode: assert isinstance(mode.value, str) class TestCitationFormatterInit: """Tests for CitationFormatter initialization.""" def test_default_mode_is_number_hyperlinks(self): """Default mode is NUMBER_HYPERLINKS.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, CitationMode, ) formatter = CitationFormatter() assert formatter.mode == CitationMode.NUMBER_HYPERLINKS def test_accepts_custom_mode(self): """Accepts custom citation mode.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, CitationMode, ) formatter = CitationFormatter(mode=CitationMode.DOMAIN_HYPERLINKS) assert formatter.mode == CitationMode.DOMAIN_HYPERLINKS def test_has_citation_pattern(self): """Has compiled citation pattern regex.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() assert formatter.citation_pattern is not None def test_has_comma_citation_pattern(self): """Has compiled comma citation pattern regex.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() assert formatter.comma_citation_pattern is not None def test_has_source_word_pattern(self): """Has compiled source word pattern regex.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() assert formatter.source_word_pattern is not None class TestCitationFormatterExtractDomain: """Tests for _extract_domain method.""" def test_extracts_simple_domain(self): """Extracts simple domain from URL.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() result = formatter._extract_domain("https://example.com/page") assert result == "example.com" def test_removes_www_prefix(self): """Removes www. prefix from domain.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() result = formatter._extract_domain("https://www.example.com/page") assert result == "example.com" def test_recognizes_known_domains(self): """Recognizes and preserves known domains.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() known_urls = [ ("https://arxiv.org/abs/1234", "arxiv.org"), ("https://github.com/user/repo", "github.com"), ("https://reddit.com/r/test", "reddit.com"), ("https://youtube.com/watch", "youtube.com"), ("https://pypi.org/project/test", "pypi.org"), ("https://milvus.io/docs", "milvus.io"), ("https://medium.com/article", "medium.com"), ] for url, expected in known_urls: result = formatter._extract_domain(url) assert result == expected, f"Failed for {url}" def test_handles_subdomain(self): """Handles subdomain in URL.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() result = formatter._extract_domain("https://docs.python.org/page") assert result == "python.org" def test_handles_invalid_url(self): """Returns 'source' for invalid URL.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() result = formatter._extract_domain("not-a-valid-url") # Should return something sensible, not crash assert isinstance(result, str) def test_handles_empty_url(self): """Handles empty URL string.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() result = formatter._extract_domain("") assert isinstance(result, str) class TestCitationFormatterFindSourcesSection: """Tests for _find_sources_section method.""" def test_finds_sources_header(self): """Finds ## Sources header.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() content = "Some content\n\n## Sources\n\n[1] Source one" result = formatter._find_sources_section(content) assert result != -1 assert content[result:].startswith("## Sources") def test_finds_references_header(self): """Finds ## References header.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() content = "Some content\n\n## References\n\n[1] Ref one" result = formatter._find_sources_section(content) assert result != -1 def test_finds_bibliography_header(self): """Finds ## Bibliography header.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() content = "Some content\n\n## Bibliography\n\n[1] Bib one" result = formatter._find_sources_section(content) assert result != -1 def test_finds_citations_header(self): """Finds ## Citations header.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() content = "Some content\n\n## Citations\n\n[1] Citation one" result = formatter._find_sources_section(content) assert result != -1 def test_case_insensitive(self): """Finds sources header case-insensitively.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() content = "Some content\n\n## SOURCES\n\n[1] Source one" result = formatter._find_sources_section(content) assert result != -1 def test_returns_minus_one_when_not_found(self): """Returns -1 when no sources section found.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() content = "Some content without sources section." result = formatter._find_sources_section(content) assert result == -1 def test_finds_single_hash_header(self): """Finds # Sources header.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() content = "Some content\n\n# Sources\n\n[1] Source one" result = formatter._find_sources_section(content) assert result != -1 def test_finds_triple_hash_header(self): """Finds ### Sources header.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() content = "Some content\n\n### Sources\n\n[1] Source one" result = formatter._find_sources_section(content) assert result != -1 class TestCitationFormatterParseSources: """Tests for _parse_sources method.""" def test_parses_single_source(self): """Parses a single source entry.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() sources_content = "[1] Test Source Title\n URL: https://example.com" result = formatter._parse_sources(sources_content) assert "1" in result assert result["1"][0] == "Test Source Title" assert result["1"][1] == "https://example.com" def test_parses_multiple_sources(self): """Parses multiple source entries.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() sources_content = """[1] First Source URL: https://first.com [2] Second Source URL: https://second.com""" result = formatter._parse_sources(sources_content) assert "1" in result assert "2" in result def test_parses_source_without_url(self): """Parses source without URL.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() sources_content = "[1] Source Without URL" result = formatter._parse_sources(sources_content) assert "1" in result assert result["1"][1] == "" # Empty URL def test_parses_comma_separated_citation_numbers(self): """Parses comma-separated citation numbers like [36, 3].""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() sources_content = ( "[36, 3] Shared Source Title\n URL: https://shared.com" ) result = formatter._parse_sources(sources_content) assert "36" in result assert "3" in result assert result["36"][0] == "Shared Source Title" assert result["3"][0] == "Shared Source Title" def test_returns_empty_dict_for_empty_content(self): """Returns empty dict for empty content.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() result = formatter._parse_sources("") assert result == {} class TestCitationFormatterFormatDocument: """Tests for format_document method.""" def test_returns_unchanged_for_no_hyperlinks_mode(self): """Returns unchanged content in NO_HYPERLINKS mode.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, CitationMode, ) formatter = CitationFormatter(mode=CitationMode.NO_HYPERLINKS) content = "Text with [1] citation.\n\n## Sources\n\n[1] Source\n URL: https://test.com" result = formatter.format_document(content) assert result == content def test_returns_unchanged_when_no_sources_section(self): """Returns unchanged when no sources section found.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() content = "Text with [1] citation but no sources section." result = formatter.format_document(content) assert result == content def test_formats_number_hyperlinks(self): """Formats citations with number hyperlinks.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, CitationMode, ) formatter = CitationFormatter(mode=CitationMode.NUMBER_HYPERLINKS) content = "See [1] for details.\n\n## Sources\n\n[1] Test Source\n URL: https://test.com" result = formatter.format_document(content) assert "[[1]](https://test.com)" in result def test_formats_domain_hyperlinks(self): """Formats citations with domain hyperlinks.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, CitationMode, ) formatter = CitationFormatter(mode=CitationMode.DOMAIN_HYPERLINKS) content = "See [1] for details.\n\n## Sources\n\n[1] Test Source\n URL: https://example.com/page" result = formatter.format_document(content) assert "example.com" in result def test_preserves_sources_section(self): """Preserves the sources section unchanged.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() sources_section = ( "## Sources\n\n[1] Test Source\n URL: https://test.com" ) content = f"See [1] here.\n\n{sources_section}" result = formatter.format_document(content) assert sources_section in result class TestCitationFormatterFormatNumberHyperlinks: """Tests for _format_number_hyperlinks method.""" def test_formats_single_citation(self): """Formats a single citation with number hyperlink.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() content = "Text with [1] here." sources = {"1": ("Title", "https://example.com")} result = formatter._format_number_hyperlinks(content, sources) assert "[[1]](https://example.com)" in result def test_formats_comma_separated_citations(self): """Formats comma-separated citations.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() content = "Text with [1, 2] here." sources = { "1": ("Title 1", "https://one.com"), "2": ("Title 2", "https://two.com"), } result = formatter._format_number_hyperlinks(content, sources) assert "[[1]](https://one.com)" in result assert "[[2]](https://two.com)" in result def test_formats_source_word_pattern(self): """Formats 'Source X' patterns.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() content = "According to Source 1, this is true." sources = {"1": ("Title", "https://example.com")} result = formatter._format_number_hyperlinks(content, sources) assert "[[1]](https://example.com)" in result def test_leaves_citation_without_url_unchanged(self): """Leaves citation without URL unchanged.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() content = "Text with [1] here." sources = {"1": ("Title", "")} # No URL result = formatter._format_number_hyperlinks(content, sources) assert "[1]" in result # Should not have hyperlink assert "[[1]](" not in result class TestCitationFormatterFormatDomainHyperlinks: """Tests for _format_domain_hyperlinks method.""" def test_replaces_number_with_domain(self): """Replaces citation number with domain.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() content = "Text with [1] here." sources = {"1": ("Title", "https://github.com/repo")} result = formatter._format_domain_hyperlinks(content, sources) assert "github.com" in result def test_handles_comma_separated_citations(self): """Handles comma-separated citations with domains.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() content = "Text with [1, 2] here." sources = { "1": ("Title 1", "https://github.com"), "2": ("Title 2", "https://arxiv.org"), } result = formatter._format_domain_hyperlinks(content, sources) assert "github.com" in result assert "arxiv.org" in result class TestCitationFormatterFormatDomainIdHyperlinks: """Tests for _format_domain_id_hyperlinks method.""" def test_no_id_for_single_domain_citation(self): """No ID suffix when only one citation from domain.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() content = "Text with [1] here." sources = {"1": ("Title", "https://github.com/repo")} result = formatter._format_domain_id_hyperlinks(content, sources) assert "[[github.com]](" in result assert "-1" not in result.split("github.com")[1].split("]")[0] def test_adds_id_for_multiple_domain_citations(self): """Adds ID suffix when multiple citations from same domain.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() content = "Text with [1] and [2] here." sources = { "1": ("Title 1", "https://github.com/repo1"), "2": ("Title 2", "https://github.com/repo2"), } result = formatter._format_domain_id_hyperlinks(content, sources) assert "github.com-1" in result assert "github.com-2" in result class TestCitationFormatterFormatDomainIdAlwaysHyperlinks: """Tests for _format_domain_id_always_hyperlinks method.""" def test_always_adds_id(self): """Always adds ID suffix even for single citation.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() content = "Text with [1] here." sources = {"1": ("Title", "https://github.com/repo")} result = formatter._format_domain_id_always_hyperlinks(content, sources) assert "github.com-1" in result class TestCitationFormatterUnicodeBrackets: """Tests for handling Unicode lenticular brackets.""" def test_matches_unicode_lenticular_brackets(self): """Matches Unicode lenticular brackets 【】.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() content = "Text with 【1】 here." sources = {"1": ("Title", "https://example.com")} result = formatter._format_number_hyperlinks(content, sources) assert "[[1]](https://example.com)" in result class TestQuartoExporterInit: """Tests for QuartoExporter initialization.""" def test_can_instantiate(self): """Can instantiate QuartoExporter.""" from local_deep_research.text_optimization.citation_formatter import ( QuartoExporter, ) exporter = QuartoExporter() assert exporter is not None def test_has_citation_pattern(self): """Has citation pattern regex.""" from local_deep_research.text_optimization.citation_formatter import ( QuartoExporter, ) exporter = QuartoExporter() assert exporter.citation_pattern is not None def test_has_comma_citation_pattern(self): """Has comma citation pattern regex.""" from local_deep_research.text_optimization.citation_formatter import ( QuartoExporter, ) exporter = QuartoExporter() assert exporter.comma_citation_pattern is not None class TestQuartoExporterExportToQuarto: """Tests for export_to_quarto method.""" def test_adds_yaml_header(self): """Adds YAML front matter header.""" from local_deep_research.text_optimization.citation_formatter import ( QuartoExporter, ) exporter = QuartoExporter() content = "# My Report\n\nSome content." result = exporter.export_to_quarto(content) assert result.startswith("---") assert "title:" in result def test_uses_provided_title(self): """Uses provided title in YAML header.""" from local_deep_research.text_optimization.citation_formatter import ( QuartoExporter, ) exporter = QuartoExporter() content = "Some content." result = exporter.export_to_quarto(content, title="Custom Title") assert "Custom Title" in result def test_extracts_title_from_content(self): """Extracts title from content if not provided.""" from local_deep_research.text_optimization.citation_formatter import ( QuartoExporter, ) exporter = QuartoExporter() content = "# My Research Report\n\nSome content." result = exporter.export_to_quarto(content) assert "My Research Report" in result def test_converts_citations_to_quarto_format(self): """Converts [1] citations to [@ref1] format.""" from local_deep_research.text_optimization.citation_formatter import ( QuartoExporter, ) exporter = QuartoExporter() content = "See [1] for details." result = exporter.export_to_quarto(content) assert "[@ref1]" in result def test_converts_comma_separated_citations(self): """Converts comma-separated citations to Quarto format.""" from local_deep_research.text_optimization.citation_formatter import ( QuartoExporter, ) exporter = QuartoExporter() content = "See [1, 2, 3] for details." result = exporter.export_to_quarto(content) assert "@ref1" in result assert "@ref2" in result assert "@ref3" in result def test_adds_bibliography_note(self): """Adds bibliography file note.""" from local_deep_research.text_optimization.citation_formatter import ( QuartoExporter, ) exporter = QuartoExporter() content = ( "Content.\n\n## Sources\n\n[1] Source One\n URL: https://test.com" ) result = exporter.export_to_quarto(content) assert "references.bib" in result class TestQuartoExporterGenerateBibliography: """Tests for _generate_bibliography method.""" def test_generates_bibtex_entries(self): """Generates BibTeX entries from sources.""" from local_deep_research.text_optimization.citation_formatter import ( QuartoExporter, ) exporter = QuartoExporter() content = "[1] Test Source Title\n URL: https://example.com" result = exporter._generate_bibliography(content) assert "@misc{ref1" in result # BibTeX uses braces and quotes: title = "{Title}" assert "Test Source Title" in result assert "title" in result def test_includes_url_in_bibtex(self): """Includes URL in BibTeX entry.""" from local_deep_research.text_optimization.citation_formatter import ( QuartoExporter, ) exporter = QuartoExporter() content = "[1] Test Source Title\n URL: https://example.com" result = exporter._generate_bibliography(content) assert "url = {https://example.com}" in result def test_generates_multiple_entries(self): """Generates multiple BibTeX entries.""" from local_deep_research.text_optimization.citation_formatter import ( QuartoExporter, ) exporter = QuartoExporter() content = "[1] First Source\n URL: https://first.com\n[2] Second Source\n URL: https://second.com" result = exporter._generate_bibliography(content) assert "@misc{ref1" in result assert "@misc{ref2" in result class TestRISExporterInit: """Tests for RISExporter initialization.""" def test_can_instantiate(self): """Can instantiate RISExporter.""" from local_deep_research.text_optimization.citation_formatter import ( RISExporter, ) exporter = RISExporter() assert exporter is not None def test_has_sources_pattern(self): """Has sources pattern regex.""" from local_deep_research.text_optimization.citation_formatter import ( RISExporter, ) exporter = RISExporter() assert exporter.sources_pattern is not None class TestRISExporterExportToRIS: """Tests for export_to_ris method.""" def test_returns_empty_for_no_sources(self): """Returns empty string when no sources section.""" from local_deep_research.text_optimization.citation_formatter import ( RISExporter, ) exporter = RISExporter() content = "Content without sources section." result = exporter.export_to_ris(content) assert result == "" def test_finds_sources_section(self): """Finds ## Sources section.""" from local_deep_research.text_optimization.citation_formatter import ( RISExporter, ) exporter = RISExporter() content = "Content.\n\n## Sources\n\n[1] Test Source\n URL: https://test.com" result = exporter.export_to_ris(content) assert "TY - ELEC" in result def test_finds_references_section(self): """Finds ## References section.""" from local_deep_research.text_optimization.citation_formatter import ( RISExporter, ) exporter = RISExporter() content = "Content.\n\n## References\n\n[1] Test Ref\n URL: https://test.com" result = exporter.export_to_ris(content) assert "TY - ELEC" in result def test_includes_title(self): """Includes title in RIS entry.""" from local_deep_research.text_optimization.citation_formatter import ( RISExporter, ) exporter = RISExporter() content = "## Sources\n\n[1] My Test Title" result = exporter.export_to_ris(content) assert "TI -" in result def test_includes_url(self): """Includes URL in RIS entry.""" from local_deep_research.text_optimization.citation_formatter import ( RISExporter, ) exporter = RISExporter() content = "## Sources\n\n[1] Test Source\nURL: https://example.com" result = exporter.export_to_ris(content) assert "UR - https://example.com" in result def test_includes_end_of_reference(self): """Includes ER - end of reference marker.""" from local_deep_research.text_optimization.citation_formatter import ( RISExporter, ) exporter = RISExporter() content = "## Sources\n\n[1] Test Source" result = exporter.export_to_ris(content) assert "ER -" in result def test_includes_language(self): """Includes language field in RIS.""" from local_deep_research.text_optimization.citation_formatter import ( RISExporter, ) exporter = RISExporter() content = "## Sources\n\n[1] Test Source" result = exporter.export_to_ris(content) assert "LA - en" in result class TestRISExporterCreateRISEntry: """Tests for _create_ris_entry method.""" def test_creates_entry_with_id(self): """Creates RIS entry with reference ID.""" from local_deep_research.text_optimization.citation_formatter import ( RISExporter, ) exporter = RISExporter() result = exporter._create_ris_entry("1", "Test Title") assert "ID - ref1" in result def test_extracts_year_from_text(self): """Extracts year from text if present.""" from local_deep_research.text_optimization.citation_formatter import ( RISExporter, ) exporter = RISExporter() result = exporter._create_ris_entry("1", "Test Title published in 2023") assert "PY - 2023" in result def test_extracts_doi_from_metadata(self): """Extracts DOI from metadata.""" from local_deep_research.text_optimization.citation_formatter import ( RISExporter, ) exporter = RISExporter() result = exporter._create_ris_entry( "1", "Test Title", metadata={"doi": "10.1234/test"} ) assert "DO - 10.1234/test" in result def test_extracts_publisher_from_github_url(self): """Extracts GitHub as publisher from URL.""" from local_deep_research.text_optimization.citation_formatter import ( RISExporter, ) exporter = RISExporter() result = exporter._create_ris_entry( "1", "Test Title", url="https://github.com/user/repo" ) assert "PB - GitHub" in result def test_extracts_publisher_from_arxiv_url(self): """Extracts arXiv as publisher from URL.""" from local_deep_research.text_optimization.citation_formatter import ( RISExporter, ) exporter = RISExporter() result = exporter._create_ris_entry( "1", "Test Title", url="https://arxiv.org/abs/1234" ) assert "PB - arXiv" in result class TestLaTeXExporterInit: """Tests for LaTeXExporter initialization.""" def test_can_instantiate(self): """Can instantiate LaTeXExporter.""" from local_deep_research.text_optimization.citation_formatter import ( LaTeXExporter, ) exporter = LaTeXExporter() assert exporter is not None def test_has_citation_pattern(self): """Has citation pattern regex.""" from local_deep_research.text_optimization.citation_formatter import ( LaTeXExporter, ) exporter = LaTeXExporter() assert exporter.citation_pattern is not None def test_has_heading_patterns(self): """Has heading patterns list.""" from local_deep_research.text_optimization.citation_formatter import ( LaTeXExporter, ) exporter = LaTeXExporter() assert len(exporter.heading_patterns) > 0 def test_has_emphasis_patterns(self): """Has emphasis patterns list.""" from local_deep_research.text_optimization.citation_formatter import ( LaTeXExporter, ) exporter = LaTeXExporter() assert len(exporter.emphasis_patterns) > 0 class TestLaTeXExporterExportToLatex: """Tests for export_to_latex method.""" def test_adds_document_class(self): """Adds documentclass declaration.""" from local_deep_research.text_optimization.citation_formatter import ( LaTeXExporter, ) exporter = LaTeXExporter() result = exporter.export_to_latex("Content") assert "\\documentclass" in result def test_adds_begin_document(self): """Adds begin{document}.""" from local_deep_research.text_optimization.citation_formatter import ( LaTeXExporter, ) exporter = LaTeXExporter() result = exporter.export_to_latex("Content") assert "\\begin{document}" in result def test_adds_end_document(self): """Adds end{document}.""" from local_deep_research.text_optimization.citation_formatter import ( LaTeXExporter, ) exporter = LaTeXExporter() result = exporter.export_to_latex("Content") assert "\\end{document}" in result def test_converts_h1_to_section(self): """Converts # heading to \\section.""" from local_deep_research.text_optimization.citation_formatter import ( LaTeXExporter, ) exporter = LaTeXExporter() result = exporter.export_to_latex("# My Heading\n\nContent") assert "\\section{My Heading}" in result def test_converts_h2_to_subsection(self): """Converts ## heading to \\subsection.""" from local_deep_research.text_optimization.citation_formatter import ( LaTeXExporter, ) exporter = LaTeXExporter() result = exporter.export_to_latex("## My Subheading\n\nContent") assert "\\subsection{My Subheading}" in result def test_converts_h3_to_subsubsection(self): """Converts ### heading to \\subsubsection.""" from local_deep_research.text_optimization.citation_formatter import ( LaTeXExporter, ) exporter = LaTeXExporter() result = exporter.export_to_latex("### My Sub-Subheading\n\nContent") assert "\\subsubsection{My Sub-Subheading}" in result def test_converts_bold_to_textbf(self): """Converts **bold** to \\textbf.""" from local_deep_research.text_optimization.citation_formatter import ( LaTeXExporter, ) exporter = LaTeXExporter() result = exporter.export_to_latex("This is **bold** text.") assert "\\textbf{bold}" in result def test_converts_italic_to_textit(self): """Converts *italic* to \\textit.""" from local_deep_research.text_optimization.citation_formatter import ( LaTeXExporter, ) exporter = LaTeXExporter() result = exporter.export_to_latex("This is *italic* text.") assert "\\textit{italic}" in result def test_converts_code_to_texttt(self): """Converts `code` to \\texttt.""" from local_deep_research.text_optimization.citation_formatter import ( LaTeXExporter, ) exporter = LaTeXExporter() result = exporter.export_to_latex("This is `code` text.") assert "\\texttt{code}" in result def test_converts_citations_to_cite(self): """Converts [1] to \\cite{1}.""" from local_deep_research.text_optimization.citation_formatter import ( LaTeXExporter, ) exporter = LaTeXExporter() result = exporter.export_to_latex("See [1] for details.") assert "\\cite{1}" in result def test_adds_bibliography(self): """Adds bibliography section when sources exist.""" from local_deep_research.text_optimization.citation_formatter import ( LaTeXExporter, ) exporter = LaTeXExporter() content = "Content.\n\n## Sources\n\n[1] Test Source\n URL: https://test.com" result = exporter.export_to_latex(content) assert "\\begin{thebibliography}" in result assert "\\end{thebibliography}" in result class TestLaTeXExporterConvertLists: """Tests for _convert_lists method.""" def test_converts_bullet_to_item(self): """Converts - bullet to \\item.""" from local_deep_research.text_optimization.citation_formatter import ( LaTeXExporter, ) exporter = LaTeXExporter() result = exporter._convert_lists("- First item\n- Second item") assert "\\item First item" in result assert "\\item Second item" in result def test_adds_itemize_environment(self): """Adds itemize environment around list.""" from local_deep_research.text_optimization.citation_formatter import ( LaTeXExporter, ) exporter = LaTeXExporter() result = exporter._convert_lists("- First item\n- Second item") assert "\\begin{itemize}" in result assert "\\end{itemize}" in result class TestLaTeXExporterEscapeLatex: """Tests for _escape_latex method.""" def test_escapes_ampersand(self): """Escapes & character.""" from local_deep_research.text_optimization.citation_formatter import ( LaTeXExporter, ) exporter = LaTeXExporter() result = exporter._escape_latex("A & B") assert "\\&" in result def test_escapes_percent(self): """Escapes % character.""" from local_deep_research.text_optimization.citation_formatter import ( LaTeXExporter, ) exporter = LaTeXExporter() result = exporter._escape_latex("50%") assert "\\%" in result def test_escapes_dollar(self): """Escapes $ character.""" from local_deep_research.text_optimization.citation_formatter import ( LaTeXExporter, ) exporter = LaTeXExporter() result = exporter._escape_latex("$100") assert "\\$" in result def test_escapes_hash(self): """Escapes # character.""" from local_deep_research.text_optimization.citation_formatter import ( LaTeXExporter, ) exporter = LaTeXExporter() result = exporter._escape_latex("#1") assert "\\#" in result def test_escapes_underscore(self): """Escapes _ character.""" from local_deep_research.text_optimization.citation_formatter import ( LaTeXExporter, ) exporter = LaTeXExporter() result = exporter._escape_latex("file_name") assert "\\_" in result def test_escapes_braces(self): """Escapes { and } characters.""" from local_deep_research.text_optimization.citation_formatter import ( LaTeXExporter, ) exporter = LaTeXExporter() result = exporter._escape_latex("{test}") assert "\\{" in result assert "\\}" in result def test_escapes_tilde(self): """Escapes ~ character.""" from local_deep_research.text_optimization.citation_formatter import ( LaTeXExporter, ) exporter = LaTeXExporter() result = exporter._escape_latex("~test") assert "\\textasciitilde{}" in result def test_escapes_caret(self): """Escapes ^ character.""" from local_deep_research.text_optimization.citation_formatter import ( LaTeXExporter, ) exporter = LaTeXExporter() result = exporter._escape_latex("x^2") assert "\\textasciicircum{}" in result class TestLaTeXExporterCreateBibliography: """Tests for _create_bibliography method.""" def test_returns_empty_for_no_sources(self): """Returns empty string when no sources section.""" from local_deep_research.text_optimization.citation_formatter import ( LaTeXExporter, ) exporter = LaTeXExporter() result = exporter._create_bibliography("Content without sources.") assert result == "" def test_creates_bibitem_entries(self): """Creates bibitem entries for sources.""" from local_deep_research.text_optimization.citation_formatter import ( LaTeXExporter, ) exporter = LaTeXExporter() content = "## Sources\n\n[1] Test Source Title" result = exporter._create_bibliography(content) assert "\\bibitem{1}" in result def test_includes_url_in_bibitem(self): """Includes URL in bibitem entry.""" from local_deep_research.text_optimization.citation_formatter import ( LaTeXExporter, ) exporter = LaTeXExporter() content = ( "## Sources\n\n[1] Test Source Title\n URL: https://example.com" ) result = exporter._create_bibliography(content) assert "\\url{https://example.com}" in result class TestCitationFormatterFormatDocumentSplit: """Tests for format_document_split — answer / sources tuple return.""" def test_returns_tuple_with_answer_and_sources(self): """Happy path: returns hyperlinked answer and raw sources block.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, CitationMode, ) formatter = CitationFormatter(mode=CitationMode.NUMBER_HYPERLINKS) content = ( "See [1] for details.\n\n" "## Sources\n\n[1] Test Source\n URL: https://test.com" ) answer, sources = formatter.format_document_split(content) assert "[[1]](https://test.com)" in answer assert sources.startswith("## Sources") assert "Test Source" in sources def test_returns_empty_sources_when_no_sources_section(self): """No Sources section in input → returns (content, '').""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, CitationMode, ) formatter = CitationFormatter(mode=CitationMode.NUMBER_HYPERLINKS) content = "Just an answer with [1] but no Sources section." answer, sources = formatter.format_document_split(content) assert answer == content assert sources == "" def test_returns_unchanged_in_no_hyperlinks_mode(self): """NO_HYPERLINKS mode short-circuits to (content, '').""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, CitationMode, ) formatter = CitationFormatter(mode=CitationMode.NO_HYPERLINKS) content = ( "Text with [1].\n\n## Sources\n\n[1] Source\n URL: https://x.com" ) answer, sources = formatter.format_document_split(content) assert answer == content assert sources == "" def test_format_document_compat_equals_concatenation(self): """format_document() == answer + sources from format_document_split.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, CitationMode, ) formatter = CitationFormatter(mode=CitationMode.NUMBER_HYPERLINKS) content = ( "Body with [1] cite.\n\n" "## Sources\n\n[1] One\n URL: https://one.com" ) full = formatter.format_document(content) answer, sources = formatter.format_document_split(content) assert full == answer + sources class TestCitationFormatterApplyInlineHyperlinks: """Tests for apply_inline_hyperlinks — structured-source delegation.""" def test_replaces_bracket_refs_with_markdown_links(self): """[1] [2] become [[1]](url) [[2]](url) given a structured list.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() content = "See [1] and [2]." sources = [ {"index": "1", "title": "First", "url": "https://a.com"}, {"index": "2", "title": "Second", "url": "https://b.com"}, ] result = formatter.apply_inline_hyperlinks(content, sources) assert "[[1]](https://a.com)" in result assert "[[2]](https://b.com)" in result def test_handles_missing_index_gracefully(self): """[7] left unchanged when no source with index 7.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() content = "See [1] and [7]." sources = [ {"index": "1", "title": "Only", "url": "https://a.com"}, ] result = formatter.apply_inline_hyperlinks(content, sources) assert "[[1]](https://a.com)" in result # [7] should remain as-is — formatter passes through unknown indices assert "[7]" in result def test_handles_comma_lists(self): """[1, 2] becomes individual links via comma_citation_pattern.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() content = "Per [1, 2] this is true." sources = [ {"index": "1", "title": "First", "url": "https://a.com"}, {"index": "2", "title": "Second", "url": "https://b.com"}, ] result = formatter.apply_inline_hyperlinks(content, sources) # _format_number_hyperlinks expands comma lists into individual links assert "https://a.com" in result assert "https://b.com" in result def test_skips_when_no_bracket_refs(self): """Content without [N] refs returns unchanged.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() content = "Plain text with no citations." sources = [ {"index": "1", "title": "Unused", "url": "https://x.com"}, ] result = formatter.apply_inline_hyperlinks(content, sources) assert result == content def test_returns_content_unchanged_when_sources_empty(self): """Empty sources list short-circuits to content.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() content = "See [1]." assert formatter.apply_inline_hyperlinks(content, []) == content def test_returns_empty_string_when_content_empty(self): """Empty content returns empty string, no crash.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() sources = [{"index": "1", "title": "x", "url": "https://x.com"}] assert formatter.apply_inline_hyperlinks("", sources) == "" assert formatter.apply_inline_hyperlinks(None, sources) == "" def test_skips_sources_with_missing_url(self): """Sources missing url are filtered out before delegation.""" from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() content = "See [1] and [2]." sources = [ {"index": "1", "title": "Has URL", "url": "https://a.com"}, {"index": "2", "title": "No URL"}, # missing url key ] result = formatter.apply_inline_hyperlinks(content, sources) assert "[[1]](https://a.com)" in result # [2] left unchanged because that source was filtered assert "[2]" in result def test_accepts_link_key_for_searxng_compatibility(self): """Regression: Searxng-sourced results use 'link' not 'url'. search_engine_searxng.py emits ``{"link": url, "title": ..., "snippet": ...}``. The langgraph-agent strategy's collector stores these dicts verbatim in ``all_links_of_system`` and the chat-mode fallback path (``apply_inline_hyperlinks``) consumed them. Looking up only ``s["url"]`` silently dropped every Searxng-sourced citation — the answer body shipped with plain ``[N]`` brackets even though the Sources section beneath was fully populated. Both keys must be accepted. """ from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() content = "Per [1] and [2], yes." sources = [ # Searxng shape {"index": "1", "title": "Searxng result", "link": "https://sx.com"}, # Other-engine shape (still works) {"index": "2", "title": "Other engine", "url": "https://o.com"}, ] result = formatter.apply_inline_hyperlinks(content, sources) assert "[[1]](https://sx.com)" in result, ( "Searxng-style 'link' key must produce a hyperlink" ) assert "[[2]](https://o.com)" in result def test_prefers_url_over_link_when_both_present(self): """When a source carries both keys, ``url`` wins. The langgraph collector copies the raw engine dict and additionally sets ``r['link'] = r['url']`` when only ``url`` is present; other callers may set both for legacy reasons. The canonical destination is ``url`` — fall back to ``link`` only when ``url`` is missing. """ from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, ) formatter = CitationFormatter() content = "See [1]." sources = [ { "index": "1", "title": "Both", "url": "https://canonical.com", "link": "https://legacy.com", } ] result = formatter.apply_inline_hyperlinks(content, sources) assert "[[1]](https://canonical.com)" in result def test_dispatches_on_self_mode_for_domain_hyperlinks(self): """Regression: ``apply_inline_hyperlinks`` was hard-coded to the NUMBER_HYPERLINKS formatter, so the chat-mode fallback path ignored ``report.citation_format``. A user who picked DOMAIN_HYPERLINKS in settings still got ``[[1]](url)`` on every chat answer instead of ``[[arxiv.org]](url)``. """ from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, CitationMode, ) formatter = CitationFormatter(mode=CitationMode.DOMAIN_HYPERLINKS) content = "Per [1] and [2]." sources = [ {"index": "1", "title": "T", "url": "https://arxiv.org/abs/123"}, {"index": "2", "title": "T", "url": "https://nytimes.com/a"}, ] result = formatter.apply_inline_hyperlinks(content, sources) assert "[[arxiv.org]](https://arxiv.org/abs/123)" in result assert "[[nytimes.com]](https://nytimes.com/a)" in result assert "[[1]](" not in result # must NOT fall back to NUMBER mode def test_dispatches_on_self_mode_for_domain_id_hyperlinks(self): from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, CitationMode, ) formatter = CitationFormatter(mode=CitationMode.DOMAIN_ID_HYPERLINKS) content = "See [1] and [2]." sources = [ {"index": "1", "title": "T", "url": "https://arxiv.org/abs/1"}, {"index": "2", "title": "T", "url": "https://arxiv.org/abs/2"}, ] result = formatter.apply_inline_hyperlinks(content, sources) # Smart domain numbering: same domain twice → [arxiv.org-1] / # [arxiv.org-2] (or similar — the exact suffix is the formatter's # business; we assert it does NOT collapse to plain [N] mode). assert "arxiv.org" in result assert "[[1]](" not in result def test_dispatches_on_self_mode_for_source_tagged_hyperlinks(self): from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, CitationMode, ) formatter = CitationFormatter( mode=CitationMode.SOURCE_TAGGED_HYPERLINKS ) content = "Per [1] and [2]." sources = [ {"index": "1", "title": "T", "url": "https://arxiv.org/abs/1"}, # Library hit: collection_name in metadata should win over # the URL-derived domain tag. { "index": "2", "title": "T", "url": "https://example.com/doc", "metadata": {"collection_name": "mypapers"}, }, ] result = formatter.apply_inline_hyperlinks(content, sources) # arxiv URL → [arxiv-1] (URLClassifier tag), collection → [mypapers-2]. assert "arxiv-1" in result assert "mypapers-2" in result def test_no_hyperlinks_mode_returns_content_unchanged(self): from local_deep_research.text_optimization.citation_formatter import ( CitationFormatter, CitationMode, ) formatter = CitationFormatter(mode=CitationMode.NO_HYPERLINKS) content = "Per [1] and [2]." sources = [ {"index": "1", "title": "T", "url": "https://a.com"}, {"index": "2", "title": "T", "url": "https://b.com"}, ] assert formatter.apply_inline_hyperlinks(content, sources) == content