|
| 1 | +"""Glued local-model tool JSON argument parsing.""" |
| 2 | + |
| 3 | +import json |
| 4 | +from types import SimpleNamespace |
| 5 | +from unittest.mock import Mock |
| 6 | + |
| 7 | +import pytest |
| 8 | + |
| 9 | +from cecli.coders.base_coder import Coder |
| 10 | +from cecli.tools.grep import Tool as GrepTool |
| 11 | +from cecli.tools.utils.helpers import merge_glued_json_objects, parse_tool_arguments |
| 12 | + |
| 13 | + |
| 14 | +def test_parse_tool_arguments_merges_glued_objects_with_empty_fragments(): |
| 15 | + raw = '{"limit": 15}{}{"path": "."}' |
| 16 | + assert parse_tool_arguments(raw) == {"limit": 15, "path": "."} |
| 17 | + |
| 18 | + |
| 19 | +def test_parse_tool_arguments_merges_grep_style_glued_args(): |
| 20 | + raw = ( |
| 21 | + '{"limit": 15}{}{"searches": [{"file_pattern": "*.md", ' |
| 22 | + '"pattern": "TODO|FIXME", "use_regex": true}]}' |
| 23 | + ) |
| 24 | + out = parse_tool_arguments(raw) |
| 25 | + assert out["limit"] == 15 |
| 26 | + assert out["searches"][0]["pattern"] == "TODO|FIXME" |
| 27 | + |
| 28 | + |
| 29 | +def test_merge_glued_returns_none_for_non_object_chunks(): |
| 30 | + assert merge_glued_json_objects(['["a"]', '{"b": 1}']) is None |
| 31 | + |
| 32 | + |
| 33 | +def test_expand_concatenated_json_merges_instead_of_splitting(monkeypatch): |
| 34 | + """Dogfood: DeepSeek ``{…}{}{…}`` must not become three tool calls.""" |
| 35 | + |
| 36 | + class MiniCoder(Coder): |
| 37 | + def __init__(self): |
| 38 | + pass |
| 39 | + |
| 40 | + coder = MiniCoder.__new__(MiniCoder) |
| 41 | + tool_call = SimpleNamespace( |
| 42 | + id="call-1", |
| 43 | + function=SimpleNamespace( |
| 44 | + name="ls", |
| 45 | + arguments='{"limit": 15}{}{"path": "."}', |
| 46 | + ), |
| 47 | + ) |
| 48 | + expanded = coder._expand_concatenated_json([tool_call]) |
| 49 | + assert len(expanded) == 1 |
| 50 | + assert json.loads(expanded[0].function.arguments) == {"limit": 15, "path": "."} |
| 51 | + assert expanded[0].id == "call-1" |
| 52 | + |
| 53 | + |
| 54 | +def test_grep_format_output_empty_searches_does_not_crash_tool_footer(): |
| 55 | + coder = SimpleNamespace( |
| 56 | + io=SimpleNamespace(tool_error=Mock(), tool_output=Mock(), tool_warning=Mock()), |
| 57 | + verbose=False, |
| 58 | + pretty=False, |
| 59 | + tui=lambda: None, |
| 60 | + ) |
| 61 | + tool_response = SimpleNamespace( |
| 62 | + function=SimpleNamespace( |
| 63 | + name="Grep", |
| 64 | + arguments='{"limit": 15}{}{"searches": []}', |
| 65 | + ), |
| 66 | + ) |
| 67 | + GrepTool.format_output( |
| 68 | + coder, |
| 69 | + mcp_server=SimpleNamespace(name="Local"), |
| 70 | + tool_response=tool_response, |
| 71 | + ) |
| 72 | + assert coder.io.tool_error.called |
0 commit comments