"""Comprehensive coverage tests for json_utils and type_utils. Tests every public and private function in: - local_deep_research.utilities.json_utils - local_deep_research.utilities.type_utils Pure-function tests -- no complex mocks required. """ import json from unittest.mock import MagicMock import pytest from local_deep_research.utilities.json_utils import ( _clean_llm_json_artifacts, _extract_by_brackets, _remove_think_tags, _strip_code_fences, extract_json, get_llm_response_text, ) from local_deep_research.utilities.type_utils import to_bool MODULE_JSON = "local_deep_research.utilities.json_utils" MODULE_TYPE = "local_deep_research.utilities.type_utils" # =================================================================== # get_llm_response_text # =================================================================== class TestGetLlmResponseText: """Cover .content, .text, str() fallback, think-tag removal, None.""" def test_content_attribute_string(self): obj = MagicMock() obj.content = "hello from content" assert get_llm_response_text(obj) == "hello from content" def test_text_attribute_string(self): """Falls to .text when .content is absent.""" obj = MagicMock(spec=[]) # spec=[] -> no .content obj.text = "hello from text" result = get_llm_response_text(obj) assert result == "hello from text" def test_plain_string_passthrough(self): assert get_llm_response_text("raw string") == "raw string" def test_think_tags_removed_from_content(self): obj = MagicMock() obj.content = "internal reasoningvisible answer" assert get_llm_response_text(obj) == "visible answer" def test_none_returns_empty_string(self): assert get_llm_response_text(None) == "" def test_content_none_falls_to_text(self): """When .content is None, the .text branch is tried.""" obj = MagicMock() obj.content = None obj.text = "fallback text" assert get_llm_response_text(obj) == "fallback text" def test_text_none_falls_to_str(self): """When both .content and .text are None, str(response) is used.""" obj = MagicMock(spec=[]) obj.text = None result = get_llm_response_text(obj) assert isinstance(result, str) def test_non_string_content_converted(self): """Non-string .content is converted via str().""" obj = MagicMock() obj.content = 12345 assert get_llm_response_text(obj) == "12345" def test_integer_input_uses_str(self): assert get_llm_response_text(42) == "42" def test_multiple_think_blocks(self): obj = MagicMock() obj.content = "aXbY" assert get_llm_response_text(obj) == "XY" # =================================================================== # extract_json # =================================================================== class TestExtractJson: """Cover valid JSON, code-fenced JSON, surrounding text, malformed JSON, expected_type filtering, empty/None inputs.""" # --- valid JSON ------------------------------------------------- def test_valid_dict(self): assert extract_json('{"a": 1}') == {"a": 1} def test_valid_list(self): assert extract_json("[1, 2, 3]") == [1, 2, 3] def test_valid_nested(self): text = '{"x": [1, {"y": 2}]}' assert extract_json(text) == {"x": [1, {"y": 2}]} # --- JSON inside code fences ------------------------------------ def test_json_in_json_fence(self): text = '```json\n{"key": "val"}\n```' assert extract_json(text) == {"key": "val"} def test_json_in_plain_fence(self): text = '```\n{"key": "val"}\n```' assert extract_json(text) == {"key": "val"} # --- JSON with surrounding prose -------------------------------- def test_prose_around_dict(self): text = 'Here is the result: {"score": 9} -- done.' assert extract_json(text) == {"score": 9} def test_prose_around_list(self): text = "The indices are: [0, 3, 5] as computed." assert extract_json(text, expected_type=list) == [0, 3, 5] # --- malformed JSON cleaned up ---------------------------------- def test_trailing_comma_in_dict(self): text = '{"a": 1, "b": 2,}' assert extract_json(text) == {"a": 1, "b": 2} def test_trailing_comma_in_list(self): text = "[1, 2, 3,]" assert extract_json(text) == [1, 2, 3] def test_js_line_comments(self): text = '{\n"a": 1, // first\n"b": 2 // second\n}' assert extract_json(text) == {"a": 1, "b": 2} def test_ellipsis_entry_removed(self): text = "[1, 2, 3, ...]" assert extract_json(text, expected_type=list) == [1, 2, 3] # --- expected_type filtering ------------------------------------ def test_expected_dict_returns_dict(self): assert extract_json('{"k": 1}', expected_type=dict) == {"k": 1} def test_expected_list_returns_list(self): assert extract_json("[1]", expected_type=list) == [1] def test_expected_list_but_got_dict_falls_to_bracket(self): """Direct parse gives dict; bracket extraction for [] finds inner list.""" text = '{"items": [10, 20]}' result = extract_json(text, expected_type=list) assert result == [10, 20] def test_expected_dict_but_got_list_returns_none(self): """Plain list when dict expected and no inner dict exists.""" assert extract_json("[1, 2]", expected_type=dict) is None # --- empty / None ----------------------------------------------- def test_empty_string(self): assert extract_json("") is None def test_whitespace_only(self): assert extract_json(" \n\t ") is None def test_none_input(self): assert extract_json(None) is None # --- scalar JSON values are not returned ------------------------ def test_scalar_number(self): assert extract_json("42") is None def test_scalar_string(self): assert extract_json('"hello"') is None def test_scalar_bool(self): assert extract_json("true") is None def test_scalar_null(self): assert extract_json("null") is None # --- deepseek-style think+fence pattern ------------------------- def test_think_then_fence(self): text = 'reasoning\n```json\n{"answer": "yes"}\n```' assert extract_json(text) == {"answer": "yes"} # --- completely invalid text ------------------------------------ def test_no_json_at_all(self): assert extract_json("Hello, this is plain text.") is None def test_brackets_with_garbage(self): assert extract_json("{not json at all!}") is None # =================================================================== # _remove_think_tags # =================================================================== class TestRemoveThinkTags: """Paired tags, orphaned tags, no tags, empty input.""" def test_paired_tags_removed(self): assert _remove_think_tags("xy") == "y" def test_multiline_content_removed(self): text = "\nline1\nline2\nresult" assert _remove_think_tags(text) == "result" def test_orphaned_closing_tag(self): assert _remove_think_tags("beforeafter") == "beforeafter" def test_orphaned_opening_tag(self): assert _remove_think_tags("beforeafter") == "beforeafter" def test_no_tags(self): assert _remove_think_tags("no tags here") == "no tags here" def test_empty_string(self): assert _remove_think_tags("") == "" def test_multiple_paired_blocks(self): text = "aXbY" assert _remove_think_tags(text) == "XY" def test_only_think_tags_returns_empty(self): assert _remove_think_tags("all gone") == "" def test_result_is_stripped(self): assert _remove_think_tags(" x y ") == "y" # =================================================================== # _strip_code_fences # =================================================================== class TestStripCodeFences: """```json fences, plain ``` fences, no fences.""" def test_json_fence(self): text = '```json\n{"a": 1}\n```' assert _strip_code_fences(text) == '{"a": 1}' def test_plain_fence(self): text = '```\n{"a": 1}\n```' assert _strip_code_fences(text) == '{"a": 1}' def test_no_fences(self): text = '{"a": 1}' assert _strip_code_fences(text) == '{"a": 1}' def test_json_fence_preferred_over_plain(self): """```json is checked before plain ```.""" text = '```json\n{"first": 1}\n```\n```\n{"second": 2}\n```' result = _strip_code_fences(text) assert '{"first": 1}' in result def test_only_two_backtick_groups_returns_original(self): """Single ``` without a matching close returns original text.""" text = "some ``` text" assert _strip_code_fences(text) == text def test_json_fence_no_closing(self): text = '```json\n{"key": "val"}' result = _strip_code_fences(text) assert '{"key": "val"}' in result def test_json_fence_with_whitespace(self): text = '```json\n {"a": 1} \n```' assert _strip_code_fences(text) == '{"a": 1}' # =================================================================== # _extract_by_brackets # =================================================================== class TestExtractByBrackets: """Dict brackets, list brackets, nested, no brackets.""" def test_dict_brackets(self): assert _extract_by_brackets('x{"a":1}y', "{", "}") == '{"a":1}' def test_list_brackets(self): assert _extract_by_brackets("x[1,2]y", "[", "]") == "[1,2]" def test_nested_brackets(self): text = '{"a": {"b": 1}}' assert _extract_by_brackets(text, "{", "}") == text def test_no_opening_bracket(self): assert _extract_by_brackets("no brackets", "{", "}") is None def test_no_closing_bracket(self): assert _extract_by_brackets("{no close", "{", "}") is None def test_close_before_open(self): """Closing bracket appears before opening -- end <= start.""" assert _extract_by_brackets("}text{", "{", "}") is None def test_empty_string(self): assert _extract_by_brackets("", "{", "}") is None def test_adjacent_brackets(self): assert _extract_by_brackets("prefix{}suffix", "{", "}") == "{}" def test_multiple_opens_uses_first(self): text = "{ extra { inner }}" result = _extract_by_brackets(text, "{", "}") assert result.startswith("{") def test_multiple_closes_uses_last(self): text = '{"a": {"b": 1}} }' result = _extract_by_brackets(text, "{", "}") assert result.endswith("}") # =================================================================== # _clean_llm_json_artifacts # =================================================================== class TestCleanLlmJsonArtifacts: """Trailing commas, JS comments, ellipsis entries.""" def test_trailing_comma_before_close_brace(self): text = '{"a": 1,}' result = _clean_llm_json_artifacts(text) assert json.loads(result) == {"a": 1} def test_trailing_comma_before_close_bracket(self): text = "[1, 2,]" result = _clean_llm_json_artifacts(text) assert json.loads(result) == [1, 2] def test_trailing_comma_with_whitespace(self): text = '{"a": 1 , \n }' result = _clean_llm_json_artifacts(text) assert json.loads(result) == {"a": 1} def test_inline_comment_removed(self): text = '{"a": 1 // comment\n}' result = _clean_llm_json_artifacts(text) assert "//" not in result assert json.loads(result) == {"a": 1} def test_multiple_comments(self): text = '{\n"a": 1, // first\n"b": 2 // second\n}' result = _clean_llm_json_artifacts(text) assert json.loads(result) == {"a": 1, "b": 2} def test_trailing_ellipsis_removed(self): text = "[1, 2, ...]" result = _clean_llm_json_artifacts(text) assert "..." not in result def test_quoted_ellipsis_between_items(self): text = '[1, "...", 2]' result = _clean_llm_json_artifacts(text) assert "..." not in result def test_ellipsis_between_items_keeps_one_comma(self): """The comma-preserving regex handles , ... , correctly.""" text = "[1, ..., 2]" result = _clean_llm_json_artifacts(text) assert "..." not in result def test_single_slash_not_touched(self): """A single / is not a comment.""" text = '{"path": "/api/v1"}' assert _clean_llm_json_artifacts(text) == text def test_clean_json_unchanged(self): text = '{"a": 1, "b": [2, 3]}' assert _clean_llm_json_artifacts(text) == text def test_all_artifacts_combined(self): text = "[\n1, // comment\n2,\n...,\n]" result = _clean_llm_json_artifacts(text) assert "//" not in result assert "..." not in result parsed = json.loads(result) assert parsed == [1, 2] # =================================================================== # to_bool (from type_utils) # =================================================================== class TestToBool: """Cover every documented truthy/falsy string, types, defaults.""" # --- truthy strings --------------------------------------------- @pytest.mark.parametrize("val", ["true", "True", "TRUE", "tRuE"]) def test_true_variants(self, val): assert to_bool(val) is True @pytest.mark.parametrize("val", ["yes", "Yes", "YES"]) def test_yes_variants(self, val): assert to_bool(val) is True def test_one_string(self): assert to_bool("1") is True @pytest.mark.parametrize("val", ["on", "On", "ON"]) def test_on_variants(self, val): assert to_bool(val) is True @pytest.mark.parametrize("val", ["enabled", "Enabled", "ENABLED"]) def test_enabled_variants(self, val): assert to_bool(val) is True # --- falsy strings ---------------------------------------------- @pytest.mark.parametrize("val", ["false", "False", "FALSE"]) def test_false_variants(self, val): assert to_bool(val) is False @pytest.mark.parametrize("val", ["no", "No", "NO"]) def test_no_variants(self, val): assert to_bool(val) is False def test_zero_string(self): assert to_bool("0") is False @pytest.mark.parametrize("val", ["off", "Off", "OFF"]) def test_off_variants(self, val): assert to_bool(val) is False @pytest.mark.parametrize("val", ["disabled", "Disabled", "DISABLED"]) def test_disabled_variants(self, val): assert to_bool(val) is False # --- whitespace handling ---------------------------------------- def test_leading_trailing_whitespace(self): assert to_bool(" true ") is True def test_whitespace_false(self): assert to_bool(" false ") is False # --- None uses default ------------------------------------------ def test_none_default_false(self): assert to_bool(None) is False def test_none_default_true(self): assert to_bool(None, default=True) is True # --- bool pass-through ------------------------------------------ def test_bool_true_passthrough(self): assert to_bool(True) is True def test_bool_false_passthrough(self): assert to_bool(False) is False # --- integer input ---------------------------------------------- def test_int_one(self): assert to_bool(1) is True def test_int_zero(self): assert to_bool(0) is False def test_int_negative(self): """Negative int is truthy via bool().""" assert to_bool(-1) is True # --- unknown strings use the truthy set ------------------------- def test_unknown_string_returns_false(self): """A string not in the truthy set returns False.""" assert to_bool("maybe") is False def test_empty_string_returns_false(self): assert to_bool("") is False # --- default ignored for non-None values ------------------------ def test_default_ignored_for_string(self): assert to_bool("false", default=True) is False def test_default_ignored_for_bool(self): assert to_bool(False, default=True) is False