diff --git a/ra_aid/prompts.py b/ra_aid/prompts.py index dc21772..07a0e6f 100644 --- a/ra_aid/prompts.py +++ b/ra_aid/prompts.py @@ -196,6 +196,8 @@ Instructions: 4. Use delete_key_facts to remove any key facts that no longer apply. 5. Do not add features not explicitly required. 6. Only create or modify files directly related to this task. +7. Use file_str_replace and write_file_tool for simple file modifications. +8. Delegate to run_programming_task for more complex programming tasks. Testing: diff --git a/tests/ra_aid/tools/test_file_str_replace.py b/tests/ra_aid/tools/test_file_str_replace.py new file mode 100644 index 0000000..181e076 --- /dev/null +++ b/tests/ra_aid/tools/test_file_str_replace.py @@ -0,0 +1,174 @@ +import os +import pytest +from pathlib import Path +from unittest.mock import patch, mock_open +from ra_aid.tools.file_str_replace import file_str_replace + +@pytest.fixture +def temp_test_dir(tmp_path): + """Create a temporary test directory.""" + test_dir = tmp_path / "test_replace_dir" + test_dir.mkdir(exist_ok=True) + return test_dir + +def test_basic_replacement(temp_test_dir): + """Test basic string replacement functionality.""" + test_file = temp_test_dir / "test.txt" + initial_content = "Hello world! This is a test." + test_file.write_text(initial_content) + + result = file_str_replace.invoke({ + "filepath": str(test_file), + "old_str": "world", + "new_str": "universe" + }) + + assert result["success"] is True + assert test_file.read_text() == "Hello universe! This is a test." + assert "Successfully replaced" in result["message"] + +def test_file_not_found(): + """Test handling of non-existent file.""" + result = file_str_replace.invoke({ + "filepath": "nonexistent.txt", + "old_str": "test", + "new_str": "replacement" + }) + + assert result["success"] is False + assert "File not found" in result["message"] + +def test_string_not_found(temp_test_dir): + """Test handling of string not present in file.""" + test_file = temp_test_dir / "test.txt" + test_file.write_text("Hello world!") + + result = file_str_replace.invoke({ + "filepath": str(test_file), + "old_str": "nonexistent", + "new_str": "replacement" + }) + + assert result["success"] is False + assert "String not found" in result["message"] + +def test_multiple_occurrences(temp_test_dir): + """Test handling of multiple string occurrences.""" + test_file = temp_test_dir / "test.txt" + test_file.write_text("test test test") + + result = file_str_replace.invoke({ + "filepath": str(test_file), + "old_str": "test", + "new_str": "replacement" + }) + + assert result["success"] is False + assert "appears" in result["message"] + assert "must be unique" in result["message"] + +def test_empty_strings(temp_test_dir): + """Test handling of empty strings.""" + test_file = temp_test_dir / "test.txt" + test_file.write_text("Hello world!") + + # Test empty old string + result1 = file_str_replace.invoke({ + "filepath": str(test_file), + "old_str": "", + "new_str": "replacement" + }) + assert result1["success"] is False + + # Test empty new string + result2 = file_str_replace.invoke({ + "filepath": str(test_file), + "old_str": "world", + "new_str": "" + }) + assert result2["success"] is True + assert test_file.read_text() == "Hello !" + +def test_special_characters(temp_test_dir): + """Test handling of special characters.""" + test_file = temp_test_dir / "test.txt" + initial_content = "Hello\nworld!\t\r\nSpecial chars: $@#%" + test_file.write_text(initial_content) + + result = file_str_replace.invoke({ + "filepath": str(test_file), + "old_str": "Special chars: $@#%", + "new_str": "Replaced!" + }) + + assert result["success"] is True + assert "Special chars: $@#%" not in test_file.read_text() + assert "Replaced!" in test_file.read_text() + +@patch('pathlib.Path.read_text') +def test_io_error(mock_read_text, temp_test_dir): + """Test handling of IO errors during read.""" + # Create and write to file first + test_file = temp_test_dir / "test.txt" + test_file.write_text("some test content") + + # Then mock read_text to raise error + mock_read_text.side_effect = IOError("Failed to read file") + + result = file_str_replace.invoke({ + "filepath": str(test_file), + "old_str": "test", + "new_str": "replacement" + }) + + assert result["success"] is False + assert "Failed to read file" in result["message"] + +def test_permission_error(temp_test_dir): + """Test handling of permission errors.""" + test_file = temp_test_dir / "readonly.txt" + test_file.write_text("test content") + os.chmod(test_file, 0o444) # Make file read-only + + try: + result = file_str_replace.invoke({ + "filepath": str(test_file), + "old_str": "test", + "new_str": "replacement" + }) + + assert result["success"] is False + assert "Permission" in result["message"] or "Error" in result["message"] + finally: + os.chmod(test_file, 0o644) # Restore permissions for cleanup + +def test_unicode_strings(temp_test_dir): + """Test handling of Unicode strings.""" + test_file = temp_test_dir / "unicode.txt" + initial_content = "Hello 世界! Unicode テスト" + test_file.write_text(initial_content) + + result = file_str_replace.invoke({ + "filepath": str(test_file), + "old_str": "世界", + "new_str": "ワールド" + }) + + assert result["success"] is True + assert "世界" not in test_file.read_text() + assert "ワールド" in test_file.read_text() + +def test_long_string_truncation(temp_test_dir): + """Test handling and truncation of very long strings.""" + test_file = temp_test_dir / "test.txt" + long_string = "x" * 100 + test_file.write_text(f"prefix {long_string} suffix") + + result = file_str_replace.invoke({ + "filepath": str(test_file), + "old_str": long_string, + "new_str": "replaced" + }) + + assert result["success"] is True + assert test_file.read_text() == "prefix replaced suffix"