"""High-value tests for utilities/json_utils.py pure logic.""" import json import unittest from unittest.mock import MagicMock from local_deep_research.utilities.json_utils import ( extract_json, get_llm_response_text, _clean_llm_json_artifacts, _extract_by_brackets, _remove_think_tags, _strip_code_fences, ) class TestGetLlmResponseText(unittest.TestCase): def test_none_returns_empty_string(self): assert get_llm_response_text(None) == "" def test_content_attribute(self): msg = MagicMock() msg.content = "hello world" assert get_llm_response_text(msg) == "hello world" def test_text_attribute_when_no_content(self): msg = MagicMock(spec=[]) msg.text = "from text" assert get_llm_response_text(msg) == "from text" def test_str_fallback(self): assert get_llm_response_text(42) == "42" def test_think_tags_removed(self): msg = MagicMock() msg.content = "before inner after" assert get_llm_response_text(msg) == "before after" def test_content_none_falls_to_text(self): msg = MagicMock() msg.content = None msg.text = "fallback" assert get_llm_response_text(msg) == "fallback" def test_non_string_content_converted(self): msg = MagicMock() msg.content = 123 result = get_llm_response_text(msg) assert result == "123" class TestRemoveThinkTags(unittest.TestCase): def test_paired_tags(self): assert _remove_think_tags("a b c") == "a c" def test_orphaned_closing(self): assert _remove_think_tags("text more") == "text more" def test_orphaned_opening(self): assert _remove_think_tags("text more") == "text more" def test_multiline_think(self): text = "start \nline1\nline2\n end" assert _remove_think_tags(text) == "start end" def test_no_tags(self): assert _remove_think_tags("plain text") == "plain text" def test_empty_string(self): assert _remove_think_tags("") == "" class TestStripCodeFences(unittest.TestCase): def test_json_fence(self): text = '```json\n{"key": "val"}\n```' assert _strip_code_fences(text) == '{"key": "val"}' def test_plain_fence(self): text = '```\n{"key": "val"}\n```' assert _strip_code_fences(text) == '{"key": "val"}' def test_no_fence(self): text = '{"key": "val"}' assert _strip_code_fences(text) == text def test_json_fence_with_surrounding_prose(self): text = 'Here is the result:\n```json\n{"a": 1}\n```\nDone.' assert _strip_code_fences(text) == '{"a": 1}' def test_unclosed_fence_returns_original(self): text = "```only one fence" assert _strip_code_fences(text) == text class TestExtractByBrackets(unittest.TestCase): def test_curly_brackets(self): text = 'prefix {"a": 1} suffix' assert _extract_by_brackets(text, "{", "}") == '{"a": 1}' def test_square_brackets(self): text = "before [1, 2, 3] after" assert _extract_by_brackets(text, "[", "]") == "[1, 2, 3]" def test_nested_brackets(self): text = '{"a": {"b": 1}}' assert _extract_by_brackets(text, "{", "}") == '{"a": {"b": 1}}' def test_no_brackets_returns_none(self): assert _extract_by_brackets("no brackets", "{", "}") is None def test_mismatched_order_returns_none(self): assert _extract_by_brackets("} before {", "{", "}") is None def test_only_open_returns_none(self): assert _extract_by_brackets("{ no close", "{", "}") is None class TestCleanLlmJsonArtifacts(unittest.TestCase): def test_trailing_comma_before_brace(self): text = '{"a": 1,}' assert _clean_llm_json_artifacts(text) == '{"a": 1}' def test_trailing_comma_before_bracket(self): text = "[1, 2, 3,]" assert _clean_llm_json_artifacts(text) == "[1, 2, 3]" def test_line_comments(self): text = '{"a": 1 // comment\n}' result = _clean_llm_json_artifacts(text) assert "//" not in result def test_ellipsis_entries(self): text = '["a", ..., "b"]' result = _clean_llm_json_artifacts(text) assert "..." not in result parsed = json.loads(result) assert parsed == ["a", "b"] def test_quoted_ellipsis(self): text = '["a", "...", "b"]' result = _clean_llm_json_artifacts(text) assert "..." not in result parsed = json.loads(result) assert parsed == ["a", "b"] def test_no_artifacts_unchanged(self): text = '{"a": 1}' assert _clean_llm_json_artifacts(text) == text class TestExtractJsonDirect(unittest.TestCase): def test_none_returns_none(self): assert extract_json(None) is None def test_empty_string_returns_none(self): assert extract_json("") is None def test_whitespace_only_returns_none(self): assert extract_json(" ") is None def test_valid_dict(self): assert extract_json('{"key": "val"}') == {"key": "val"} def test_valid_list(self): assert extract_json("[1, 2, 3]") == [1, 2, 3] def test_invalid_json_returns_none(self): assert extract_json("not json at all") is None class TestExtractJsonExpectedType(unittest.TestCase): def test_expect_dict_gets_dict(self): result = extract_json('{"a": 1}', expected_type=dict) assert result == {"a": 1} def test_expect_list_gets_list(self): result = extract_json("[1, 2]", expected_type=list) assert result == [1, 2] def test_expect_dict_skips_list(self): # Text has both a list and a dict text = 'Here is [1] and {"a": 1}' result = extract_json(text, expected_type=dict) assert isinstance(result, dict) def test_expect_list_skips_dict(self): text = 'Here is {"a": 1} and [1, 2]' result = extract_json(text, expected_type=list) assert isinstance(result, list) def test_type_mismatch_falls_through(self): # Only a list in text, but dict expected — returns None result = extract_json("[1, 2]", expected_type=dict) assert result is None class TestExtractJsonComplex(unittest.TestCase): def test_json_in_code_fence(self): text = '```json\n{"a": 1}\n```' assert extract_json(text) == {"a": 1} def test_json_with_surrounding_prose(self): text = 'The answer is: {"result": true} as shown.' assert extract_json(text) == {"result": True} def test_json_with_think_tags(self): text = 'reasoning{"answer": 42}' assert extract_json(text) == {"answer": 42} def test_json_with_trailing_comma(self): text = '{"a": 1, "b": 2,}' result = extract_json(text) assert result == {"a": 1, "b": 2} def test_json_with_comments(self): text = '{"a": 1 // this is a comment\n}' result = extract_json(text) assert result == {"a": 1} def test_nested_json(self): text = '{"outer": {"inner": [1, 2, 3]}}' result = extract_json(text) assert result == {"outer": {"inner": [1, 2, 3]}} if __name__ == "__main__": unittest.main()