import os import pytest from scripts import py_struct_tools from src import mcp_client @pytest.fixture def temp_py_file(tmp_path): p = tmp_path / "sample.py" content = """class MyClass: \"\"\"Docstring.\"\"\" def method1(self): print("m1") def top_func(): \"\"\"Top doc.\"\"\" print("top") """ p.write_text(content, encoding="utf-8") return str(p) def test_find_definition_range(): source = """class A: def m(self): pass def f(): pass """ assert py_struct_tools.find_definition_range(source, "A") == (1, 2) assert py_struct_tools.find_definition_range(source, "A.m") == (2, 2) assert py_struct_tools.find_definition_range(source, "f") == (3, 3) assert py_struct_tools.find_definition_range(source, "nonexistent") is None def test_shift_indentation(): payload = "def f():\n print('hi')" # 2-space shifted = py_struct_tools.shift_indentation(payload, 1) assert shifted == " def f():\n print('hi')" # wait, shift_indentation strips min and prepends. # Let's re-test shift_indentation logic # Original: # line 1: 'def f():' (0 indent) # line 2: ' print('hi')' (2 indent) # min_indent = 0 # Prepend 1 space: # ' def f():' # ' print('hi')' # If payload was: # def f(): # print('hi') # min_indent = 2 # target_depth = 1 # ' def f():' # ' print('hi')' payload2 = " def f():\n print('hi')" shifted2 = py_struct_tools.shift_indentation(payload2, 1) assert shifted2 == " def f():\n print('hi')" def test_py_remove_def(temp_py_file): err = py_struct_tools.py_remove_def(temp_py_file, "MyClass.method1") assert err == "" with open(temp_py_file, 'r') as f: content = f.read() assert "def method1" not in content assert "class MyClass" in content def test_py_add_def(temp_py_file): new_code = "def method2(self):\n print('m2')" err = py_struct_tools.py_add_def(temp_py_file, "MyClass", new_code, "after", "method1") assert err == "" with open(temp_py_file, 'r') as f: content = f.read() assert "def method2" in content # Check 1-space indentation assert " def method2(self):" in content def test_py_region_wrap(temp_py_file): err = py_struct_tools.py_region_wrap(temp_py_file, 6, 8, "MyRegion") assert err == "" with open(temp_py_file, 'r') as f: content = f.read() assert "#region: MyRegion" in content assert "#endregion: MyRegion" in content def test_mcp_dispatch_integration(temp_py_file): # Mock allowlist mcp_client.configure([{"path": temp_py_file}]) # Test py_remove_def result = mcp_client.dispatch("py_remove_def", {"path": temp_py_file, "name": "top_func"}) assert result == "" with open(temp_py_file, 'r') as f: content = f.read() assert "def top_func" not in content # Test py_add_def (module level top) result = mcp_client.dispatch("py_add_def", { "path": temp_py_file, "name": "", "new_content": "def head_func():\n print('head')", "anchor_type": "top" }) assert result == "" with open(temp_py_file, 'r') as f: content = f.read() assert content.startswith("def head_func") # Test py_add_def (class bottom) result = mcp_client.dispatch("py_add_def", { "path": temp_py_file, "name": "MyClass", "new_content": "def tail_method(self):\n print('tail')", "anchor_type": "bottom" }) assert result == "" with open(temp_py_file, 'r') as f: content = f.read() assert "def tail_method" in content assert " def tail_method(self):" in content # Check indent # Test py_move_def (cross-file simulated with same file) # We move method1 to after tail_method result = mcp_client.dispatch("py_move_def", { "src_path": temp_py_file, "dest_path": temp_py_file, "name": "MyClass.method1", "dest_name": "MyClass", "anchor_type": "after", "anchor_symbol": "tail_method" }) assert result == "" with open(temp_py_file, 'r') as f: content = f.read() # method1 should now be AFTER tail_method assert content.find("def method1") > content.find("def tail_method") def test_mcp_dispatch_errors(temp_py_file): mcp_client.configure([{"path": temp_py_file}]) # Non-existent symbol result = mcp_client.dispatch("py_remove_def", {"path": temp_py_file, "name": "NoSuchSymbol"}) assert "ERROR" in result or "not found" in result # Denied path result = mcp_client.dispatch("py_remove_def", {"path": "C:/windows/system32/cmd.exe", "name": "foo"}) assert "ACCESS DENIED" in result