# Copyright (c) 2025 ByteDance Ltd. and/or its affiliates # SPDX-License-Identifier: MIT """Tests for JSONEditTool.""" import json import unittest from unittest.mock import mock_open, patch from trae_agent.tools.base import ToolCallArguments from trae_agent.tools.json_edit_tool import JSONEditTool class TestJSONEditTool(unittest.IsolatedAsyncioTestCase): def setUp(self): """Set up the test environment.""" self.tool = JSONEditTool() self.test_file_path = "/test_dir/test_file.json" # Default sample data self.sample_data = { "users": [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}], "config": {"enabled": True}, } def mock_file_read(self, json_data=None): """Helper to mock file reading operations.""" if json_data is None: json_data = self.sample_data read_content = json.dumps(json_data) m_open = mock_open(read_data=read_content) # Patch open and path checks self.open_patcher = patch("builtins.open", m_open) self.exists_patcher = patch("pathlib.Path.exists", return_value=True) self.is_absolute_patcher = patch("pathlib.Path.is_absolute", return_value=True) self.open_patcher.start() self.exists_patcher.start() self.is_absolute_patcher.start() self.addCleanup(self.open_patcher.stop) self.addCleanup(self.exists_patcher.stop) self.addCleanup(self.is_absolute_patcher.stop) @patch("json.dump") async def test_set_config_value(self, mock_json_dump): """Test setting a simple configuration value.""" self.mock_file_read() result = await self.tool.execute( ToolCallArguments( { "operation": "set", "file_path": self.test_file_path, "json_path": "$.config.enabled", "value": False, } ) ) self.assertEqual(result.error_code, 0) # Verify that json.dump was called with the correct data mock_json_dump.assert_called_once() written_data = mock_json_dump.call_args[0][0] self.assertFalse(written_data["config"]["enabled"]) @patch("json.dump") async def test_update_user_name(self, mock_json_dump): """Test updating a name in a list of objects.""" self.mock_file_read() result = await self.tool.execute( ToolCallArguments( { "operation": "set", "file_path": self.test_file_path, "json_path": "$.users[0].name", "value": "Alicia", } ) ) self.assertEqual(result.error_code, 0) mock_json_dump.assert_called_once() written_data = mock_json_dump.call_args[0][0] self.assertEqual(written_data["users"][0]["name"], "Alicia") @patch("json.dump") async def test_add_new_user(self, mock_json_dump): """Test adding a new object to a list (by inserting at the end).""" self.mock_file_read() result = await self.tool.execute( ToolCallArguments( { "operation": "add", "file_path": self.test_file_path, "json_path": "$.users[2]", # Inserting at index 2 (end of list) "value": {"id": 3, "name": "Charlie"}, } ) ) self.assertEqual(result.error_code, 0) mock_json_dump.assert_called_once() written_data = mock_json_dump.call_args[0][0] self.assertEqual(len(written_data["users"]), 3) self.assertEqual(written_data["users"][2]["name"], "Charlie") @patch("json.dump") async def test_add_new_config_key(self, mock_json_dump): """Test adding a new key-value pair to an object.""" self.mock_file_read() result = await self.tool.execute( ToolCallArguments( { "operation": "add", "file_path": self.test_file_path, "json_path": "$.config.version", "value": "1.1.0", } ) ) self.assertEqual(result.error_code, 0) mock_json_dump.assert_called_once() written_data = mock_json_dump.call_args[0][0] self.assertEqual(written_data["config"]["version"], "1.1.0") @patch("json.dump") async def test_remove_user_by_index(self, mock_json_dump): """Test removing an element from a list by its index.""" self.mock_file_read() result = await self.tool.execute( ToolCallArguments( { "operation": "remove", "file_path": self.test_file_path, "json_path": "$.users[0]", } ) ) self.assertEqual(result.error_code, 0) mock_json_dump.assert_called_once() written_data = mock_json_dump.call_args[0][0] self.assertEqual(len(written_data["users"]), 1) self.assertEqual(written_data["users"][0]["name"], "Bob") @patch("json.dump") async def test_remove_config_key(self, mock_json_dump): """Test removing a key from an object.""" self.mock_file_read() result = await self.tool.execute( ToolCallArguments( { "operation": "remove", "file_path": self.test_file_path, "json_path": "$.config.enabled", } ) ) self.assertEqual(result.error_code, 0) mock_json_dump.assert_called_once() written_data = mock_json_dump.call_args[0][0] self.assertNotIn("enabled", written_data["config"]) async def test_view_operation(self): """Test the view operation to ensure it reads and returns content.""" self.mock_file_read() result = await self.tool.execute( ToolCallArguments( { "operation": "view", "file_path": self.test_file_path, "json_path": "$.users[0]", } ) ) self.assertEqual(result.error_code, 0) self.assertIn('"id": 1', result.output) self.assertIn('"name": "Alice"', result.output) async def test_error_file_not_found(self): """Test error handling when the file does not exist.""" # Mock Path.exists to return False with ( patch("pathlib.Path.exists", return_value=False), patch("pathlib.Path.is_absolute", return_value=True), ): result = await self.tool.execute( ToolCallArguments( { "operation": "view", "file_path": "/nonexistent/file.json", } ) ) self.assertEqual(result.error_code, -1) self.assertIn("File does not exist", result.error) if __name__ == "__main__": unittest.main()