Skip to main content
Glama

File Patch MCP Server

by shenning00
test_validate.py11 kB
"""Tests for validate_patch tool. Tests the patch validation functionality including: - Can apply (success=True) - Cannot apply (success=False, can_apply=False) - CRITICAL - Invalid patch format - Preview information - Security checks """ from patch_mcp.tools.validate import validate_patch class TestValidatePatch: """Test suite for validate_patch tool.""" def test_validate_can_apply(self, tmp_path): """When patch can be applied, success=True.""" # Create file file = tmp_path / "config.py" file.write_text("DEBUG = False\nLOG_LEVEL = 'INFO'\nPORT = 8000\n") # Create patch that matches patch = """--- config.py +++ config.py @@ -1,3 +1,3 @@ DEBUG = False -LOG_LEVEL = 'INFO' +LOG_LEVEL = 'DEBUG' PORT = 8000 """ result = validate_patch(str(file), patch) # CRITICAL: success=True when can apply assert result["success"] is True assert result["file_path"] == str(file) assert result["valid"] is True assert result["can_apply"] is True assert result["message"] == "Patch is valid and can be applied cleanly" # Check preview assert "preview" in result assert result["preview"]["lines_to_add"] == 1 assert result["preview"]["lines_to_remove"] == 1 assert result["preview"]["hunks"] == 1 assert result["preview"]["affected_line_range"]["start"] >= 1 assert result["preview"]["affected_line_range"]["end"] >= 1 def test_validate_cannot_apply(self, tmp_path): """When patch cannot be applied, success=False.""" # Create file with different content file = tmp_path / "config.py" file.write_text("DEBUG = False\nLOG_LEVEL = 'WARNING'\nPORT = 8000\n") # Create patch that expects different content patch = """--- config.py +++ config.py @@ -1,3 +1,3 @@ DEBUG = False -LOG_LEVEL = 'INFO' +LOG_LEVEL = 'DEBUG' PORT = 8000 """ result = validate_patch(str(file), patch) # CRITICAL: success=False when cannot apply! assert result["success"] is False assert result["file_path"] == str(file) assert result["valid"] is True assert result["can_apply"] is False assert result["error_type"] == "context_mismatch" assert "reason" in result assert "INFO" in result["reason"] or "WARNING" in result["reason"] assert result["message"] == "Patch is valid but cannot be applied to this file" # Preview should still be present assert "preview" in result def test_validate_invalid_patch(self, tmp_path): """Invalid patch format returns error.""" file = tmp_path / "config.py" file.write_text("content\n") # Invalid patch (missing headers) bad_patch = """@@ -1,1 +1,1 @@ -old +new """ result = validate_patch(str(file), bad_patch) # Should fail with invalid_patch error assert result["success"] is False assert result["valid"] is False assert result["error_type"] == "invalid_patch" assert "Invalid patch format" in result["error"] def test_validate_file_not_found(self, tmp_path): """Missing file returns file_not_found error.""" missing = tmp_path / "missing.txt" patch = """--- missing.txt +++ missing.txt @@ -1,1 +1,1 @@ -old +new """ result = validate_patch(str(missing), patch) assert result["success"] is False assert result["error_type"] == "file_not_found" def test_validate_symlink_rejected(self, tmp_path): """Symlinks should be rejected (security policy).""" # Create real file and symlink real_file = tmp_path / "real.txt" real_file.write_text("content\n") symlink = tmp_path / "link.txt" symlink.symlink_to(real_file) patch = """--- link.txt +++ link.txt @@ -1,1 +1,1 @@ -content +modified """ result = validate_patch(str(symlink), patch) assert result["success"] is False assert result["error_type"] == "symlink_error" def test_validate_binary_rejected(self, tmp_path): """Binary files should be rejected.""" binary = tmp_path / "binary.dat" binary.write_bytes(b"\x00\x01\x02" * 100) patch = """--- binary.dat +++ binary.dat @@ -1,1 +1,1 @@ -old +new """ result = validate_patch(str(binary), patch) assert result["success"] is False assert result["error_type"] == "binary_file" def test_validate_preview_information(self, tmp_path): """Preview should contain accurate information.""" file = tmp_path / "file.py" file.write_text("line1\nline2\nline3\nline4\nline5\n") patch = """--- file.py +++ file.py @@ -2,3 +2,4 @@ line2 +added line3 line4 """ result = validate_patch(str(file), patch) # Should succeed assert result["success"] is True # Check preview details preview = result["preview"] assert preview["lines_to_add"] == 1 assert preview["lines_to_remove"] == 0 assert preview["hunks"] == 1 # Affected line range should be an object assert isinstance(preview["affected_line_range"], dict) assert "start" in preview["affected_line_range"] assert "end" in preview["affected_line_range"] assert preview["affected_line_range"]["start"] > 0 assert preview["affected_line_range"]["end"] > 0 def test_validate_empty_patch(self, tmp_path): """Empty patch should be valid and applicable.""" file = tmp_path / "file.txt" file.write_text("content\n") result = validate_patch(str(file), "") # Empty patch should succeed assert result["success"] is True assert result["can_apply"] is True assert result["preview"]["hunks"] == 0 def test_validate_multiple_hunks(self, tmp_path): """Validate patch with multiple hunks.""" file = tmp_path / "file.py" file.write_text( "line1\nline2\nline3\nline4\nline5\n" "line6\nline7\nline8\nline9\nline10\n" ) patch = """--- file.py +++ file.py @@ -1,3 +1,3 @@ -line1 +line1 modified line2 line3 @@ -8,3 +8,3 @@ line8 -line9 +line9 modified line10 """ result = validate_patch(str(file), patch) assert result["success"] is True assert result["preview"]["hunks"] == 2 assert result["preview"]["lines_to_add"] == 2 assert result["preview"]["lines_to_remove"] == 2 def test_validate_context_partially_matches(self, tmp_path): """Context that only partially matches should fail.""" file = tmp_path / "file.py" file.write_text("line1\nmodified_line2\nline3\n") # Patch expects original line2 patch = """--- file.py +++ file.py @@ -1,3 +1,3 @@ line1 -line2 +line2 changed line3 """ result = validate_patch(str(file), patch) # Should fail with context mismatch assert result["success"] is False assert result["can_apply"] is False assert result["error_type"] == "context_mismatch" def test_validate_hunk_out_of_range(self, tmp_path): """Hunk that references lines beyond file should fail.""" file = tmp_path / "file.py" file.write_text("line1\nline2\n") # Patch expects more lines than file has patch = """--- file.py +++ file.py @@ -1,10 +1,10 @@ line1 line2 -line3 +line3 modified """ result = validate_patch(str(file), patch) # Should fail assert result["success"] is False assert result["can_apply"] is False def test_validate_encoding_error(self, tmp_path): """Non-UTF-8 file should return encoding error.""" # Create file with invalid UTF-8 that's not detected as binary file = tmp_path / "file.txt" # Write mostly text but with some invalid UTF-8 file.write_bytes(b"line1\nline2\n" + b"\xff\xfe" + b"\nline3\n") patch = """--- file.txt +++ file.txt @@ -1,1 +1,1 @@ -line1 +modified """ result = validate_patch(str(file), patch) # Should fail with encoding error (or binary, depending on detection) assert result["success"] is False assert result["error_type"] in ["encoding_error", "binary_file"] def test_validate_preserves_line_endings(self, tmp_path): """Validation should handle different line endings.""" file = tmp_path / "file.txt" file.write_text("line1\nline2\nline3\n") # Patch with the same content patch = """--- file.txt +++ file.txt @@ -1,3 +1,3 @@ line1 -line2 +line2 modified line3 """ result = validate_patch(str(file), patch) assert result["success"] is True assert result["can_apply"] is True def test_validate_addition_only(self, tmp_path): """Patch with only additions.""" file = tmp_path / "file.py" file.write_text("line1\nline2\n") patch = """--- file.py +++ file.py @@ -1,2 +1,3 @@ line1 +added line2 """ result = validate_patch(str(file), patch) assert result["success"] is True assert result["preview"]["lines_to_add"] == 1 assert result["preview"]["lines_to_remove"] == 0 def test_validate_removal_only(self, tmp_path): """Patch with only removals.""" file = tmp_path / "file.py" file.write_text("line1\nremove_me\nline2\n") patch = """--- file.py +++ file.py @@ -1,3 +1,2 @@ line1 -remove_me line2 """ result = validate_patch(str(file), patch) assert result["success"] is True assert result["preview"]["lines_to_add"] == 0 assert result["preview"]["lines_to_remove"] == 1 def test_validate_whitespace_sensitive(self, tmp_path): """Validation should be whitespace-sensitive.""" file = tmp_path / "file.py" file.write_text("line1\nline2 \nline3\n") # line2 has trailing space # Patch expects no trailing space patch = """--- file.py +++ file.py @@ -1,3 +1,3 @@ line1 -line2 +line2 modified line3 """ result = validate_patch(str(file), patch) # Should still work because we strip newlines but not spaces # The patch library handles this assert result["success"] is False or result["success"] is True # The exact behavior depends on whitespace handling def test_validate_reason_field_present(self, tmp_path): """When cannot apply, reason field must be present.""" file = tmp_path / "file.py" file.write_text("wrong\ncontent\n") patch = """--- file.py +++ file.py @@ -1,2 +1,2 @@ -expected +modified content """ result = validate_patch(str(file), patch) # Should fail with reason assert result["success"] is False assert result["can_apply"] is False assert "reason" in result assert isinstance(result["reason"], str) assert len(result["reason"]) > 0

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/shenning00/patch_mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server