"""Tests for json_utils module.""" from unittest.mock import Mock class TestGetLlmResponseText: """Tests for get_llm_response_text function.""" def test_extracts_content_attribute(self): """Test that .content attribute is extracted from response.""" from local_deep_research.utilities.json_utils import ( get_llm_response_text, ) response = Mock() response.content = "Hello world" assert get_llm_response_text(response) == "Hello world" def test_falls_back_to_text_attribute(self): """Test that .text is used when .content is not available.""" from local_deep_research.utilities.json_utils import ( get_llm_response_text, ) response = Mock(spec=[]) response.text = "Hello text" assert get_llm_response_text(response) == "Hello text" def test_content_takes_precedence_over_text(self): """Test that .content is preferred over .text.""" from local_deep_research.utilities.json_utils import ( get_llm_response_text, ) response = Mock() response.content = "from content" response.text = "from text" assert get_llm_response_text(response) == "from content" def test_plain_string_passthrough(self): """Test that a plain string is returned via str().""" from local_deep_research.utilities.json_utils import ( get_llm_response_text, ) assert get_llm_response_text("plain string") == "plain string" def test_none_input_returns_empty_string(self): """Test that None input returns empty string.""" from local_deep_research.utilities.json_utils import ( get_llm_response_text, ) assert get_llm_response_text(None) == "" def test_content_none_falls_through_to_text(self): """Test that content=None falls through to .text.""" from local_deep_research.utilities.json_utils import ( get_llm_response_text, ) response = Mock() response.content = None response.text = "from text" assert get_llm_response_text(response) == "from text" def test_non_string_content_converted(self): """Test that non-string .content is converted to string.""" from local_deep_research.utilities.json_utils import ( get_llm_response_text, ) response = Mock() response.content = 42 assert get_llm_response_text(response) == "42" def test_removes_paired_think_tags(self): """Test that paired ... tags are removed.""" from local_deep_research.utilities.json_utils import ( get_llm_response_text, ) response = Mock() response.content = "reasoningResult here" assert get_llm_response_text(response) == "Result here" def test_removes_orphaned_think_tags(self): """Test that orphaned opening or closing think tags are removed.""" from local_deep_research.utilities.json_utils import ( get_llm_response_text, ) response = Mock() response.content = "Result" assert get_llm_response_text(response) == "Result" def test_removes_multiline_think_tags(self): """Test that multiline think tag content is removed.""" from local_deep_research.utilities.json_utils import ( get_llm_response_text, ) response = Mock() response.content = "\nline1\nline2\n\nActual content" assert get_llm_response_text(response) == "Actual content" class TestExtractJson: """Tests for extract_json function.""" def test_pure_json_dict(self): """Test parsing a pure JSON dict string.""" from local_deep_research.utilities.json_utils import extract_json result = extract_json('{"key": "value"}') assert result == {"key": "value"} def test_pure_json_list(self): """Test parsing a pure JSON list string.""" from local_deep_research.utilities.json_utils import extract_json result = extract_json("[1, 2, 3]") assert result == [1, 2, 3] def test_nested_structures(self): """Test parsing nested JSON structures.""" from local_deep_research.utilities.json_utils import extract_json text = '{"items": [{"id": 1}, {"id": 2}]}' result = extract_json(text) assert result == {"items": [{"id": 1}, {"id": 2}]} def test_code_fence_json(self): """Test extracting JSON from ```json code fences.""" from local_deep_research.utilities.json_utils import extract_json text = '```json\n{"key": "value"}\n```' result = extract_json(text) assert result == {"key": "value"} def test_bare_code_fence(self): """Test extracting JSON from bare ``` code fences.""" from local_deep_research.utilities.json_utils import extract_json text = "```\n[1, 2, 3]\n```" result = extract_json(text) assert result == [1, 2, 3] def test_code_fence_with_surrounding_prose(self): """Test extracting JSON from code fences with text around them.""" from local_deep_research.utilities.json_utils import extract_json text = ( 'Here is the result:\n```json\n{"key": "value"}\n```\n' "Hope that helps!" ) result = extract_json(text) assert result == {"key": "value"} def test_dict_embedded_in_prose(self): """Test extracting JSON dict embedded in prose text.""" from local_deep_research.utilities.json_utils import extract_json text = 'The answer is {"key": "value"} as shown above.' result = extract_json(text) assert result == {"key": "value"} def test_list_embedded_in_prose(self): """Test extracting JSON list embedded in prose text.""" from local_deep_research.utilities.json_utils import extract_json text = "Here are the indices: [3, 0, 7, 1] sorted by relevance." result = extract_json(text, expected_type=list) assert result == [3, 0, 7, 1] def test_expected_type_list_prefers_brackets(self): """Test that expected_type=list tries [] before {}.""" from local_deep_research.utilities.json_utils import extract_json text = 'Prefix {"a": 1} and also [1, 2, 3] suffix' result = extract_json(text, expected_type=list) assert result == [1, 2, 3] def test_expected_type_dict_prefers_braces(self): """Test that expected_type=dict tries {} before [].""" from local_deep_research.utilities.json_utils import extract_json text = 'Prefix [1, 2, 3] and also {"a": 1} suffix' result = extract_json(text, expected_type=dict) assert result == {"a": 1} def test_type_mismatch_falls_through(self): """Test that type mismatch on direct parse falls to extraction.""" from local_deep_research.utilities.json_utils import extract_json text = '{"items": [1, 2, 3]}' result = extract_json(text, expected_type=list) assert result == [1, 2, 3] def test_type_mismatch_no_alternative_returns_none(self): """Test that type mismatch with no matching brackets returns None.""" from local_deep_research.utilities.json_utils import extract_json text = "just plain text no json here" result = extract_json(text, expected_type=list) assert result is None def test_think_tags_cleaned_before_parse(self): """Test that think tags are removed before JSON parsing.""" from local_deep_research.utilities.json_utils import extract_json text = 'reasoning here{"key": "value"}' result = extract_json(text) assert result == {"key": "value"} def test_none_input_returns_none(self): """Test that None input returns None.""" from local_deep_research.utilities.json_utils import extract_json assert extract_json(None) is None def test_empty_string_returns_none(self): """Test that empty string returns None.""" from local_deep_research.utilities.json_utils import extract_json assert extract_json("") is None def test_whitespace_only_returns_none(self): """Test that whitespace-only string returns None.""" from local_deep_research.utilities.json_utils import extract_json assert extract_json(" \n\t ") is None def test_no_json_returns_none(self): """Test that text without JSON returns None.""" from local_deep_research.utilities.json_utils import extract_json assert extract_json("Hello, I have no JSON here.") is None def test_invalid_json_in_brackets_returns_none(self): """Test that invalid JSON inside brackets returns None.""" from local_deep_research.utilities.json_utils import extract_json assert extract_json("{not valid json at all}") is None def test_json_scalar_returns_none(self): """Test that JSON scalars (number, string) return None.""" from local_deep_research.utilities.json_utils import extract_json assert extract_json("42") is None assert extract_json('"just a string"') is None def test_unicode_content_preserved(self): """Test that unicode content in JSON is preserved.""" from local_deep_research.utilities.json_utils import extract_json text = '{"name": "café", "city": "München"}' result = extract_json(text) assert result == {"name": "café", "city": "München"} def test_multiline_json(self): """Test parsing multiline JSON with whitespace.""" from local_deep_research.utilities.json_utils import extract_json text = '{\n "key1": "value1",\n "key2": "value2"\n}' result = extract_json(text) assert result == {"key1": "value1", "key2": "value2"} def test_expected_type_none_accepts_dict(self): """Test that expected_type=None accepts dict.""" from local_deep_research.utilities.json_utils import extract_json result = extract_json('{"a": 1}', expected_type=None) assert result == {"a": 1} def test_expected_type_none_accepts_list(self): """Test that expected_type=None accepts list.""" from local_deep_research.utilities.json_utils import extract_json result = extract_json("[1, 2]", expected_type=None) assert result == [1, 2] def test_nested_brackets_extracted_correctly(self): """Test that nested brackets use outermost match.""" from local_deep_research.utilities.json_utils import extract_json text = 'prefix {"a": {"b": [1, 2]}} suffix' result = extract_json(text) assert result == {"a": {"b": [1, 2]}} def test_list_of_dicts(self): """Test parsing a list of dictionaries.""" from local_deep_research.utilities.json_utils import extract_json text = '[{"id": 1, "name": "a"}, {"id": 2, "name": "b"}]' result = extract_json(text, expected_type=list) assert result == [ {"id": 1, "name": "a"}, {"id": 2, "name": "b"}, ] class TestExtractJsonLlmArtifacts: """Tests for LLM artifact cleaning in extract_json.""" def test_trailing_comma_in_array(self): """Test that trailing commas in arrays are handled.""" from local_deep_research.utilities.json_utils import extract_json result = extract_json("[1, 2, 3,]") assert result == [1, 2, 3] def test_trailing_comma_in_object(self): """Test that trailing commas in objects are handled.""" from local_deep_research.utilities.json_utils import extract_json result = extract_json('{"a": 1, "b": 2,}') assert result == {"a": 1, "b": 2} def test_inline_comments(self): """Test that // comments are cleaned from JSON.""" from local_deep_research.utilities.json_utils import extract_json text = "[1, 2, // this is best\n3]" result = extract_json(text, expected_type=list) assert result == [1, 2, 3] def test_ellipsis_in_array(self): """Test that ellipsis entries are removed from arrays.""" from local_deep_research.utilities.json_utils import extract_json result = extract_json("[1, 2, ...]") assert result == [1, 2] def test_quoted_ellipsis_in_array(self): """Test that quoted ellipsis in valid JSON is preserved (valid JSON).""" from local_deep_research.utilities.json_utils import extract_json # This is valid JSON, so direct parse succeeds without artifact cleaning result = extract_json('[1, 2, "..."]') assert result == [1, 2, "..."] def test_unquoted_ellipsis_in_malformed_json(self): """Test that ellipsis is cleaned from malformed JSON.""" from local_deep_research.utilities.json_utils import extract_json # Trailing comma + ellipsis makes this invalid, triggers cleaning result = extract_json("[1, 2, ...,]") assert result == [1, 2] def test_combined_artifacts(self): """Test cleaning multiple artifact types at once.""" from local_deep_research.utilities.json_utils import extract_json text = "[1, 2, // comment\n3, ...,]" result = extract_json(text, expected_type=list) assert result == [1, 2, 3] def test_clean_json_not_modified(self): """Test that valid JSON is not affected by artifact cleaning.""" from local_deep_research.utilities.json_utils import extract_json text = '{"url": "https://example.com/path", "count": 5}' result = extract_json(text) assert result == { "url": "https://example.com/path", "count": 5, } def test_trailing_comma_in_prose(self): """Test trailing comma cleanup with surrounding prose.""" from local_deep_research.utilities.json_utils import extract_json text = "The result is: [1, 2, 3,] as expected" result = extract_json(text, expected_type=list) assert result == [1, 2, 3] class TestStripCodeFences: """Tests for _strip_code_fences private helper.""" def test_json_code_fence(self): """Test stripping ```json code fences.""" from local_deep_research.utilities.json_utils import ( _strip_code_fences, ) text = '```json\n{"key": "value"}\n```' assert _strip_code_fences(text) == '{"key": "value"}' def test_bare_code_fence(self): """Test stripping bare ``` code fences.""" from local_deep_research.utilities.json_utils import ( _strip_code_fences, ) text = "```\n[1, 2, 3]\n```" assert _strip_code_fences(text) == "[1, 2, 3]" def test_no_code_fence(self): """Test that text without code fences is returned unchanged.""" from local_deep_research.utilities.json_utils import ( _strip_code_fences, ) text = '{"key": "value"}' assert _strip_code_fences(text) == '{"key": "value"}' def test_stray_backticks_not_treated_as_fence(self): """Test that stray backticks are not treated as code fences.""" from local_deep_research.utilities.json_utils import ( _strip_code_fences, ) text = "Use ``` for code blocks" assert _strip_code_fences(text) == "Use ``` for code blocks" def test_json_fence_with_surrounding_text(self): """Test code fence extraction with text before and after.""" from local_deep_research.utilities.json_utils import ( _strip_code_fences, ) text = 'Here is the JSON:\n```json\n{"a": 1}\n```\nDone!' assert _strip_code_fences(text) == '{"a": 1}' class TestExtractByBrackets: """Tests for _extract_by_brackets private helper.""" def test_extract_dict_brackets(self): """Test extracting content between { and }.""" from local_deep_research.utilities.json_utils import ( _extract_by_brackets, ) text = 'prefix {"key": "value"} suffix' result = _extract_by_brackets(text, "{", "}") assert result == '{"key": "value"}' def test_extract_list_brackets(self): """Test extracting content between [ and ].""" from local_deep_research.utilities.json_utils import ( _extract_by_brackets, ) text = "prefix [1, 2, 3] suffix" result = _extract_by_brackets(text, "[", "]") assert result == "[1, 2, 3]" def test_missing_open_bracket_returns_none(self): """Test that missing open bracket returns None.""" from local_deep_research.utilities.json_utils import ( _extract_by_brackets, ) result = _extract_by_brackets("no brackets here}", "{", "}") assert result is None def test_missing_close_bracket_returns_none(self): """Test that missing close bracket returns None.""" from local_deep_research.utilities.json_utils import ( _extract_by_brackets, ) result = _extract_by_brackets("{no close bracket", "{", "}") assert result is None def test_close_before_open_returns_none(self): """Test that close bracket before open returns None.""" from local_deep_research.utilities.json_utils import ( _extract_by_brackets, ) result = _extract_by_brackets("} before {", "{", "}") assert result is None def test_nested_brackets_uses_outermost(self): """Test that nested brackets extract outermost pair.""" from local_deep_research.utilities.json_utils import ( _extract_by_brackets, ) text = '{"a": {"b": 1}}' result = _extract_by_brackets(text, "{", "}") assert result == '{"a": {"b": 1}}'