diff --git a/tests/integration/extension/test_reflection.py b/tests/integration/extension/test_reflection.py new file mode 100644 index 000000000..932e1bda3 --- /dev/null +++ b/tests/integration/extension/test_reflection.py @@ -0,0 +1,60 @@ +"""Integration tests for the reflection tool.""" + +from unittest.mock import MagicMock + +import pytest + +from codegen.extensions.tools.reflection import perform_reflection +from codegen.sdk.core.codebase import Codebase + + +@pytest.fixture +def mock_codebase(): + """Create a mock codebase for testing.""" + return MagicMock(spec=Codebase) + + +def test_perform_reflection(mock_codebase): + """Test the reflection tool.""" + # Test with basic inputs + result = perform_reflection( + context_summary="Testing the reflection tool", + findings_so_far="Found some interesting patterns", + current_challenges="Need to improve test coverage", + reflection_focus=None, + codebase=mock_codebase, + ) + + # Verify the result structure + assert result.status == "success" + assert "reflection" in result.content + assert "Testing the reflection tool" in result.content + assert "Found some interesting patterns" in result.content + assert "Need to improve test coverage" in result.content + + # Test with a specific reflection focus + result = perform_reflection( + context_summary="Testing the reflection tool", + findings_so_far="Found some interesting patterns", + current_challenges="Need to improve test coverage", + reflection_focus="architecture", + codebase=mock_codebase, + ) + + # Verify the result includes the focus + assert result.status == "success" + assert "reflection" in result.content + assert "architecture" in result.content + + # Test with minimal inputs + result = perform_reflection( + context_summary="Minimal test", + findings_so_far="Minimal findings", + codebase=mock_codebase, + ) + + # Verify the result with minimal inputs + assert result.status == "success" + assert "reflection" in result.content + assert "Minimal test" in result.content + assert "Minimal findings" in result.content diff --git a/tests/integration/extension/test_relace_edit.py b/tests/integration/extension/test_relace_edit.py new file mode 100644 index 000000000..8ee9b7af9 --- /dev/null +++ b/tests/integration/extension/test_relace_edit.py @@ -0,0 +1,150 @@ +"""Integration tests for the relace_edit tool.""" + +import os +import tempfile +from pathlib import Path +from unittest.mock import MagicMock, patch + +import pytest + +from codegen.extensions.tools.relace_edit import relace_edit +from codegen.sdk.core.codebase import Codebase + + +@pytest.fixture +def temp_workspace(): + """Create a temporary workspace for testing.""" + with tempfile.TemporaryDirectory() as temp_dir: + # Create a simple file structure for testing + base_dir = Path(temp_dir) + + # Create a simple Python file + test_file = base_dir / "test_file.py" + test_file.write_text(""" +def hello_world(): + return 'Hello, World!' + +def goodbye_world(): + return 'Goodbye, World!' +""") + + # Create a JavaScript file + js_file = base_dir / "test_file.js" + js_file.write_text(""" +function helloWorld() { + return 'Hello, World!'; +} + +function goodbyeWorld() { + return 'Goodbye, World!'; +} +""") + + # Create a codebase from the temp directory + codebase = Codebase.from_directory(str(base_dir)) + + yield codebase + + +@pytest.mark.skipif(not os.getenv("RELACE_API"), reason="RELACE_API environment variable not set") +def test_relace_edit_real_api(temp_workspace): + """Test relace_edit with the real API. + + This test is skipped if the RELACE_API environment variable is not set. + """ + # Test editing a Python file + edit_snippet = """ +# Keep existing imports + +# Modify hello_world function +def hello_world(): + print("Starting greeting") + return 'Hello, Modified World!' + +# Keep goodbye_world function +""" + + result = relace_edit(temp_workspace, "test_file.py", edit_snippet) + assert result.status == "success" + + # Verify the file was modified correctly + from codegen.extensions.tools import view_file + + file_content = view_file(temp_workspace, "test_file.py") + assert 'print("Starting greeting")' in file_content.content + assert "Hello, Modified World!" in file_content.content + assert "Goodbye, World!" in file_content.content # Should be preserved + + +@patch("codegen.extensions.tools.relace_edit.requests.post") +def test_relace_edit_mock_api(mock_post, temp_workspace): + """Test relace_edit with a mocked API.""" + # Mock the API response + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "status": "success", + "result": """ +def hello_world(): + print("Mocked greeting") + return 'Hello, Mocked World!' + +def goodbye_world(): + return 'Goodbye, World!' +""", + } + mock_post.return_value = mock_response + + # Test editing a Python file + edit_snippet = """ +# Modify hello_world function +def hello_world(): + print("Mocked greeting") + return 'Hello, Mocked World!' + +# Keep goodbye_world function +""" + + result = relace_edit(temp_workspace, "test_file.py", edit_snippet) + assert result.status == "success" + + # Verify the API was called with the correct parameters + mock_post.assert_called_once() + args, kwargs = mock_post.call_args + assert "content" in kwargs["json"] + assert "edit_snippet" in kwargs["json"] + + # Verify the file was modified correctly + from codegen.extensions.tools import view_file + + file_content = view_file(temp_workspace, "test_file.py") + assert 'print("Mocked greeting")' in file_content.content + assert "Hello, Mocked World!" in file_content.content + + +@patch("codegen.extensions.tools.relace_edit.requests.post") +def test_relace_edit_api_error(mock_post, temp_workspace): + """Test relace_edit with API errors.""" + # Mock an API error response + mock_response = MagicMock() + mock_response.status_code = 400 + mock_response.json.return_value = {"status": "error", "error": "Invalid request"} + mock_post.return_value = mock_response + + # Test with API error + result = relace_edit(temp_workspace, "test_file.py", "# Invalid edit") + assert result.status == "error" + assert "API error" in result.error + + # Mock a network error + mock_post.side_effect = Exception("Network error") + + # Test with network error + result = relace_edit(temp_workspace, "test_file.py", "# Invalid edit") + assert result.status == "error" + assert "Network error" in result.error + + # Test with non-existent file + result = relace_edit(temp_workspace, "nonexistent_file.py", "# Edit") + assert result.status == "error" + assert "not found" in result.error diff --git a/tests/integration/extension/test_slack.py b/tests/integration/extension/test_slack.py new file mode 100644 index 000000000..470e4d13a --- /dev/null +++ b/tests/integration/extension/test_slack.py @@ -0,0 +1,99 @@ +"""Integration tests for Slack tools.""" + +from unittest.mock import MagicMock + +import pytest + +from codegen.extensions.tools.link_annotation import add_links_to_message +from codegen.sdk.core.codebase import Codebase + + +@pytest.fixture +def mock_codebase(): + """Create a mock codebase for testing.""" + codebase = MagicMock(spec=Codebase) + + # Mock the symbol lookup functionality + def mock_get_symbol(symbol_name): + if symbol_name in ["test_function", "TestClass"]: + mock_symbol = MagicMock() + mock_symbol.filepath = f"src/{symbol_name.lower()}.py" + return mock_symbol + return None + + codebase.get_symbol.side_effect = mock_get_symbol + + # Mock the file exists functionality + def mock_file_exists(filepath): + return filepath in ["src/file1.py", "src/file2.py", "src/test_function.py", "src/testclass.py"] + + codebase.file_exists.side_effect = mock_file_exists + + return codebase + + +def test_add_links_to_message(mock_codebase): + """Test adding links to a Slack message.""" + # Test with symbol names + message = "Check the `test_function` and `TestClass` implementations." + result = add_links_to_message(message, mock_codebase) + + # Verify links were added for symbols + assert "`test_function`" not in result # Should be replaced with a link + assert "`TestClass`" not in result # Should be replaced with a link + assert "src/test_function.py" in result + assert "src/testclass.py" in result + + # Test with file paths + message = "Look at `src/file1.py` and `src/file2.py`." + result = add_links_to_message(message, mock_codebase) + + # Verify links were added for files + assert "`src/file1.py`" not in result # Should be replaced with a link + assert "`src/file2.py`" not in result # Should be replaced with a link + assert "src/file1.py" in result + assert "src/file2.py" in result + + # Test with non-existent symbols and files + message = "Check `nonexistent_function` and `src/nonexistent.py`." + result = add_links_to_message(message, mock_codebase) + + # Verify no links were added for non-existent items + assert "`nonexistent_function`" in result # Should remain as is + assert "`src/nonexistent.py`" in result # Should remain as is + + # Test with mixed content + message = "Check `test_function` and regular text with `src/file1.py` and *markdown*." + result = add_links_to_message(message, mock_codebase) + + # Verify links were added only for valid symbols and files + assert "`test_function`" not in result + assert "`src/file1.py`" not in result + assert "src/test_function.py" in result + assert "src/file1.py" in result + assert "*markdown*" in result # Markdown should be preserved + + +def test_send_slack_message(): + """Test sending a message to Slack. + + Note: This is a mock test since we can't actually send messages in the test environment. + """ + # Create a mock say function + mock_say = MagicMock() + + # Create a mock codebase + mock_codebase = MagicMock(spec=Codebase) + + # Import the function directly to test + from codegen.extensions.langchain.tools import SlackSendMessageTool + + # Create the tool + tool = SlackSendMessageTool(codebase=mock_codebase, say=mock_say) + + # Test sending a message + result = tool._run("Test message") + + # Verify the message was sent + mock_say.assert_called_once() + assert "✅ Message sent successfully" in result diff --git a/tests/integration/extension/test_workspace_tools.py b/tests/integration/extension/test_workspace_tools.py new file mode 100644 index 000000000..1aa99c06e --- /dev/null +++ b/tests/integration/extension/test_workspace_tools.py @@ -0,0 +1,291 @@ +"""Integration tests for workspace tools.""" + +import tempfile +from pathlib import Path + +import pytest + +from codegen.extensions.tools import ( + create_file, + delete_file, + edit_file, + list_directory, + rename_file, + view_file, +) +from codegen.extensions.tools.replacement_edit import replacement_edit +from codegen.extensions.tools.search import search +from codegen.sdk.core.codebase import Codebase + + +@pytest.fixture +def temp_workspace(): + """Create a temporary workspace for testing.""" + with tempfile.TemporaryDirectory() as temp_dir: + # Create a simple file structure for testing + base_dir = Path(temp_dir) + + # Create a simple Python file + test_file = base_dir / "test_file.py" + test_file.write_text("def hello_world():\n return 'Hello, World!'\n") + + # Create a directory with files + test_dir = base_dir / "test_dir" + test_dir.mkdir() + (test_dir / "file1.txt").write_text("File 1 content") + (test_dir / "file2.txt").write_text("File 2 content") + + # Create a nested directory + nested_dir = test_dir / "nested" + nested_dir.mkdir() + (nested_dir / "nested_file.txt").write_text("Nested file content") + + # Create a codebase from the temp directory + codebase = Codebase.from_directory(str(base_dir)) + + yield codebase + + +def test_view_file(temp_workspace): + """Test viewing a file.""" + # Test viewing a file that exists + result = view_file(temp_workspace, "test_file.py") + assert result.status == "success" + assert "def hello_world()" in result.content + + # Test viewing a file with line numbers + result = view_file(temp_workspace, "test_file.py", line_numbers=True) + assert result.status == "success" + assert "1|def hello_world()" in result.content + + # Test viewing a file with line range + result = view_file(temp_workspace, "test_file.py", start_line=1, end_line=1) + assert result.status == "success" + assert "def hello_world()" in result.content + assert "return 'Hello, World!'" not in result.content + + # Test viewing a file that doesn't exist + result = view_file(temp_workspace, "nonexistent_file.py") + assert result.status == "error" + assert "not found" in result.error + + +def test_list_directory(temp_workspace): + """Test listing directory contents.""" + # Test listing the root directory + result = list_directory(temp_workspace, "./") + assert result.status == "success" + assert "test_file.py" in result.content + assert "test_dir" in result.content + + # Test listing a subdirectory + result = list_directory(temp_workspace, "test_dir") + assert result.status == "success" + assert "file1.txt" in result.content + assert "file2.txt" in result.content + assert "nested" in result.content + + # Test listing with depth + result = list_directory(temp_workspace, "./", depth=2) + assert result.status == "success" + assert "test_file.py" in result.content + assert "test_dir" in result.content + assert "file1.txt" in result.content + assert "file2.txt" in result.content + assert "nested" in result.content + + # Test listing a directory that doesn't exist + result = list_directory(temp_workspace, "nonexistent_dir") + assert result.status == "error" + assert "not found" in result.error + + +def test_create_file(temp_workspace): + """Test creating a file.""" + # Test creating a new file + result = create_file(temp_workspace, "new_file.py", "print('New file')") + assert result.status == "success" + + # Verify the file was created + result = view_file(temp_workspace, "new_file.py") + assert result.status == "success" + assert "print('New file')" in result.content + + # Test creating a file that already exists + result = create_file(temp_workspace, "test_file.py", "# Overwrite") + assert result.status == "error" + assert "already exists" in result.error + + # Test creating a file in a directory that doesn't exist + result = create_file(temp_workspace, "nonexistent_dir/file.py", "# Content") + assert result.status == "error" + assert "directory does not exist" in result.error + + +def test_edit_file(temp_workspace): + """Test editing a file.""" + # Test editing an existing file + result = edit_file(temp_workspace, "test_file.py", "# Modified file\ndef hello_world():\n return 'Modified!'") + assert result.status == "success" + + # Verify the file was modified + result = view_file(temp_workspace, "test_file.py") + assert result.status == "success" + assert "# Modified file" in result.content + assert "return 'Modified!'" in result.content + + # Test editing a file that doesn't exist + result = edit_file(temp_workspace, "nonexistent_file.py", "# Content") + assert result.status == "error" + assert "not found" in result.error + + +def test_delete_file(temp_workspace): + """Test deleting a file.""" + # Test deleting an existing file + result = delete_file(temp_workspace, "test_dir/file1.txt") + assert result.status == "success" + + # Verify the file was deleted + result = list_directory(temp_workspace, "test_dir") + assert result.status == "success" + assert "file1.txt" not in result.content + assert "file2.txt" in result.content + + # Test deleting a file that doesn't exist + result = delete_file(temp_workspace, "nonexistent_file.py") + assert result.status == "error" + assert "not found" in result.error + + +def test_rename_file(temp_workspace): + """Test renaming a file.""" + # Test renaming an existing file + result = rename_file(temp_workspace, "test_file.py", "renamed_file.py") + assert result.status == "success" + + # Verify the file was renamed + result = list_directory(temp_workspace, "./") + assert result.status == "success" + assert "test_file.py" not in result.content + assert "renamed_file.py" in result.content + + # Test renaming a file that doesn't exist + result = rename_file(temp_workspace, "nonexistent_file.py", "new_name.py") + assert result.status == "error" + assert "not found" in result.error + + # Test renaming to a file that already exists + # First create a file + create_file(temp_workspace, "target_file.py", "# Target file") + result = rename_file(temp_workspace, "renamed_file.py", "target_file.py") + assert result.status == "error" + assert "already exists" in result.error + + +def test_search(temp_workspace): + """Test searching the codebase.""" + # Test searching for text + result = search(temp_workspace, "Hello, World!") + assert result.status == "success" + assert len(result.matches) > 0 + assert "test_file.py" in result.matches[0]["filepath"] + + # Test searching with regex + result = search(temp_workspace, "def.*world", use_regex=True) + assert result.status == "success" + assert len(result.matches) > 0 + + # Test searching with file extensions + result = search(temp_workspace, "content", file_extensions=[".txt"]) + assert result.status == "success" + assert len(result.matches) > 0 + assert all(".txt" in match["filepath"] for match in result.matches) + + # Test searching with no matches + result = search(temp_workspace, "nonexistent_text") + assert result.status == "success" + assert len(result.matches) == 0 + + +def test_replacement_edit(temp_workspace): + """Test replacement edit.""" + # Test replacing text in a file + result = replacement_edit( + temp_workspace, + filepath="test_file.py", + pattern="Hello, World!", + replacement="Goodbye, World!", + ) + assert result.status == "success" + + # Verify the replacement was made + result = view_file(temp_workspace, "test_file.py") + assert result.status == "success" + assert "Goodbye, World!" in result.content + assert "Hello, World!" not in result.content + + # Test replacing with regex groups + result = replacement_edit( + temp_workspace, + filepath="test_file.py", + pattern=r"def (hello_world)\(\):", + replacement=r"def \1_function():", + ) + assert result.status == "success" + + # Verify the regex replacement was made + result = view_file(temp_workspace, "test_file.py") + assert result.status == "success" + assert "def hello_world_function():" in result.content + + # Test replacing with line range + # First create a multi-line file + content = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5" + create_file(temp_workspace, "lines.txt", content) + + result = replacement_edit( + temp_workspace, + filepath="lines.txt", + pattern="Line", + replacement="Modified", + start=2, + end=4, + ) + assert result.status == "success" + + # Verify only lines in the range were modified + result = view_file(temp_workspace, "lines.txt") + assert result.status == "success" + assert "Line 1" in result.content + assert "Modified 2" in result.content + assert "Modified 3" in result.content + assert "Modified 4" in result.content + assert "Line 5" in result.content + + # Test replacing with count + create_file(temp_workspace, "count.txt", "Replace Replace Replace Replace") + + result = replacement_edit( + temp_workspace, + filepath="count.txt", + pattern="Replace", + replacement="Changed", + count=2, + ) + assert result.status == "success" + + # Verify only the specified number of replacements were made + result = view_file(temp_workspace, "count.txt") + assert result.status == "success" + assert "Changed Changed Replace Replace" in result.content + + # Test replacing in a file that doesn't exist + result = replacement_edit( + temp_workspace, + filepath="nonexistent_file.py", + pattern="pattern", + replacement="replacement", + ) + assert result.status == "error" + assert "not found" in result.error