Skip to main content
Glama

Deskaid

by ezyang
test_write_file.py24 kB
#!/usr/bin/env python3 """Tests for the WriteFile subtool.""" import os import unittest from codemcp.testing import MCPEndToEndTestCase class WriteFileTest(MCPEndToEndTestCase): """Test the WriteFile subtool.""" async def test_write_file(self): """Test the WriteFile subtool, which writes to a file and automatically commits the changes.""" test_file_path = os.path.join(self.temp_dir.name, "new_file.txt") content = "New content\nLine 2" # First add the file to git to make it tracked with open(test_file_path, "w") as f: f.write("") # Add it to git await self.git_run(["add", test_file_path]) # Commit it await self.git_run(["commit", "-m", "Add empty file for WriteFile test"]) async with self.create_client_session() as session: # First initialize project to get chat_id init_result_text = await self.call_tool_assert_success( session, "codemcp", { "subtool": "InitProject", "path": self.temp_dir.name, "user_prompt": "Test initialization for write_file test", "subject_line": "test: initialize for write file test", "reuse_head_chat_id": False, }, ) # Extract chat_id from the init result chat_id = self.extract_chat_id_from_text(init_result_text) # Call the WriteFile tool with chat_id using our new helper method result_text = await self.call_tool_assert_success( session, "codemcp", { "subtool": "WriteFile", "path": test_file_path, "content": content, "description": "Create new file", "chat_id": chat_id, }, ) # Verify the success message self.assertIn("Successfully wrote to", result_text) # Verify the file was created with the correct content with open(test_file_path) as f: file_content = f.read() self.assertEqual(file_content, content + "\n") # Verify git state (working tree should be clean after automatic commit) status = await self.git_run(["status"], capture_output=True, text=True) # Use expect test to verify git status - should show clean working tree self.assertExpectedInline( status, """\ On branch main nothing to commit, working tree clean""", ) # Get the commit message of the HEAD commit commit_message = await self.git_run( ["log", "-1", "--pretty=%B"], capture_output=True, text=True ) # Normalize the chat_id in the commit message for expect test normalized_commit_message = commit_message.replace(chat_id, "test-chat-id") # Use expect test to verify the commit message format self.assertExpectedInline( normalized_commit_message, """\ test: initialize for write file test Test initialization for write_file test ```git-revs c9bcf9c (Base revision) HEAD Create new file ``` codemcp-id: test-chat-id""", ) # Second write to the same file updated_content = content + "\nAdded third line" # Call the WriteFile tool again with updated content result_text = await self.call_tool_assert_success( session, "codemcp", { "subtool": "WriteFile", "path": test_file_path, "content": updated_content, "description": "Update file with third line", "chat_id": chat_id, }, ) # Verify the success message for second write self.assertIn("Successfully wrote to", result_text) # Verify the file was updated with the correct content with open(test_file_path) as f: file_content = f.read() self.assertEqual(file_content, updated_content + "\n") # Verify git state after second write status = await self.git_run(["status"], capture_output=True, text=True) # Use expect test to verify git status - should still show clean working tree self.assertExpectedInline( status, """\ On branch main nothing to commit, working tree clean""", ) # Get the commit message of the HEAD commit after second write commit_message = await self.git_run( ["log", "-1", "--pretty=%B"], capture_output=True, text=True ) # Normalize the chat_id in the commit message for expect test normalized_commit_message = commit_message.replace(chat_id, "test-chat-id") # Use expect test to verify the commit message format for second write self.assertExpectedInline( normalized_commit_message, """\ test: initialize for write file test Test initialization for write_file test ```git-revs c9bcf9c (Base revision) 49bf8ff Create new file HEAD Update file with third line ``` codemcp-id: test-chat-id""", ) async def test_create_new_file_with_write_file(self): """Test creating a new file that doesn't exist yet with WriteFile.""" # Path to a new file that doesn't exist yet, within the git repository new_file_path = os.path.join(self.temp_dir.name, "completely_new_file.txt") self.assertFalse( os.path.exists(new_file_path), "Test file should not exist initially", ) async with self.create_client_session() as session: # First initialize project to get chat_id init_result_text = await self.call_tool_assert_success( session, "codemcp", { "subtool": "InitProject", "path": self.temp_dir.name, "user_prompt": "Test initialization for creating new file", "subject_line": "test: initialize for new file test", "reuse_head_chat_id": False, }, ) # Extract chat_id from the init result chat_id = self.extract_chat_id_from_text(init_result_text) # Create a new file using our helper method result_text = await self.call_tool_assert_success( session, "codemcp", { "subtool": "WriteFile", "path": new_file_path, "content": "This is a brand new file", "description": "Create a new file with WriteFile", "chat_id": chat_id, }, ) # Check that the operation succeeded self.assertIn("Successfully wrote to", result_text) # Verify the file was created self.assertTrue( os.path.exists(new_file_path), "File was not created even though operation reported success", ) # Check content with open(new_file_path) as f: content = f.read() self.assertEqual(content, "This is a brand new file\n") # Verify the file was added to git ls_files_output = await self.git_run( ["ls-files", new_file_path], capture_output=True, text=True ) # The new file should be tracked in git self.assertTrue( ls_files_output, "New file was created but not added to git", ) async def test_write_to_untracked_file(self): """Test that writes to untracked files are rejected.""" # Create an untracked file (not added to git) untracked_file_path = os.path.join( self.temp_dir.name, "untracked_for_write.txt", ) with open(untracked_file_path, "w") as f: f.write("Initial content in untracked file") # Verify the file exists but is not tracked by git file_exists = os.path.exists(untracked_file_path) self.assertTrue(file_exists, "Test file should exist on filesystem") # Check that the file is untracked ls_files_output = await self.git_run( ["ls-files", untracked_file_path], capture_output=True, text=True ) self.assertEqual(ls_files_output, "", "File should not be tracked by git") # Save original content and modification time for comparison with open(untracked_file_path) as f: original_content = f.read() original_mtime = os.path.getmtime(untracked_file_path) async with self.create_client_session() as session: # First initialize project to get chat_id init_result_text = await self.call_tool_assert_success( session, "codemcp", { "subtool": "InitProject", "path": self.temp_dir.name, "user_prompt": "Test initialization for untracked file test", "subject_line": "test: initialize for untracked file test", "reuse_head_chat_id": False, }, ) # Extract chat_id from the init result chat_id = self.extract_chat_id_from_text(init_result_text) # Try to write to the untracked file new_content = "This content should not be written to untracked file" result_text = await self.call_tool_assert_error( session, "codemcp", { "subtool": "WriteFile", "path": untracked_file_path, "content": new_content, "description": "Attempt to write to untracked file", "chat_id": chat_id, }, ) # The error message format has changed since we're now directly calling the subtool # instead of going through the codemcp tool self.assertIn( "File is not tracked by git", result_text, "Error message should indicate the file is not tracked by git", ) # Verify the file content has not changed with open(untracked_file_path) as f: actual_content = f.read() self.assertEqual( original_content, actual_content, "File content should not change when operation is rejected", ) # Verify file modification time was not changed current_mtime = os.path.getmtime(untracked_file_path) self.assertEqual( current_mtime, original_mtime, "File modification time should not have changed", ) async def test_write_file_outside_tracked_paths(self): """Test that codemcp properly handles writing to paths outside tracked paths.""" # Create a subdirectory but don't add it to git subdir_path = os.path.join(self.temp_dir.name, "untrackeddir") os.makedirs(subdir_path, exist_ok=True) new_file_path = os.path.join(subdir_path, "newfile.txt") async with self.create_client_session() as session: # First initialize project to get chat_id init_result_text = await self.call_tool_assert_success( session, "codemcp", { "subtool": "InitProject", "path": self.temp_dir.name, "user_prompt": "Test initialization for untracked directory test", "subject_line": "test: initialize for untracked directory test", "reuse_head_chat_id": False, }, ) # Extract chat_id from the init result chat_id = self.extract_chat_id_from_text(init_result_text) # Try to write a new file in the untracked directory await self.call_tool_assert_success( session, "codemcp", { "subtool": "WriteFile", "path": new_file_path, "content": "New file in untracked directory", "description": "Attempt to create file in untracked directory", "chat_id": chat_id, }, ) # Since we're using call_tool_assert_success, we know the operation succeeded # Check if the directory and file are now tracked in git await self.git_run(["status"], capture_output=True, text=True) # The file should exist self.assertTrue( os.path.exists(new_file_path), "File was not created even though operation reported success", ) # SECURITY CHECK: If writing to untracked directories succeeds, # both the directory and file should be tracked in git ls_files_output = await self.git_run( ["ls-files", new_file_path], capture_output=True, text=True ) # IMPORTANT: The file should be tracked in git after writing self.assertTrue( ls_files_output, "SECURITY VULNERABILITY: Successfully wrote to untracked directory" " but did not add file to git", ) async def test_write_file_with_tilde_path(self): """Test that WriteFile correctly handles paths with tilde (~) expansion.""" # Create a temporary directory in the user's home directory specifically for this test home_dir = os.path.expanduser("~") home_temp_dir_path = os.path.join(home_dir, f"codemcp_test_tilde_{os.getpid()}") os.makedirs(home_temp_dir_path, exist_ok=True) try: # Initialize a git repository in this directory await self.git_run(["init", "-b", "main"], cwd=home_temp_dir_path) await self.git_run( ["config", "user.email", "test@example.com"], cwd=home_temp_dir_path ) await self.git_run( ["config", "user.name", "Test User"], cwd=home_temp_dir_path ) # Create initial commit readme_path = os.path.join(home_temp_dir_path, "README.md") with open(readme_path, "w") as f: f.write("# Test Repository for tilde expansion\n") # Create a codemcp.toml file (required for permission checks) codemcp_toml_path = os.path.join(home_temp_dir_path, "codemcp.toml") with open(codemcp_toml_path, "w") as f: f.write("") await self.git_run( ["add", "README.md", "codemcp.toml"], cwd=home_temp_dir_path ) await self.git_run( ["commit", "-m", "Initial commit"], cwd=home_temp_dir_path ) # Create a relative path using tilde rel_path = os.path.relpath(home_temp_dir_path, home_dir) tilde_path = os.path.join("~", rel_path, "tilde_test_file.txt") # Expected absolute path after tilde expansion expected_abs_path = os.path.expanduser(tilde_path) self.assertEqual( os.path.abspath(expected_abs_path), os.path.abspath( os.path.join(home_temp_dir_path, "tilde_test_file.txt") ), ) # Content to write content = "This file was created with a tilde path" async with self.create_client_session() as session: # First initialize project to get chat_id init_result_text = await self.call_tool_assert_success( session, "codemcp", { "subtool": "InitProject", "path": home_temp_dir_path, "user_prompt": "Test initialization for tilde path test", "subject_line": "test: initialize for tilde path test", "reuse_head_chat_id": False, }, ) # Extract chat_id from the init result chat_id = self.extract_chat_id_from_text(init_result_text) # Call the WriteFile tool with the tilde path await self.call_tool_assert_success( session, "codemcp", { "subtool": "WriteFile", "path": tilde_path, "content": content, "description": "Create file using tilde path", "chat_id": chat_id, }, ) # Verify the file was created self.assertTrue( os.path.exists(expected_abs_path), "File should exist after writing with tilde path", ) # Verify content with open(expected_abs_path) as f: actual_content = f.read() # WriteFile adds a newline at the end of files self.assertEqual(actual_content, content + "\n") # Verify the file was added to git ls_files_output = await self.git_run( ["ls-files", "tilde_test_file.txt"], capture_output=True, text=True, cwd=home_temp_dir_path, ) # The file should be tracked in git self.assertTrue( ls_files_output, "New file created with tilde path should be tracked in git", ) finally: # Clean up the temporary directory import shutil if os.path.exists(home_temp_dir_path): shutil.rmtree(home_temp_dir_path) async def test_user_prompt_with_markdown_code_block(self): """Test handling of user prompt that contains a Markdown code block with triple backticks.""" test_file_path = os.path.join( self.temp_dir.name, "markdown_code_block_test.txt" ) content = "File created from a prompt with a code block" # Create placeholder file and add to git with open(test_file_path, "w") as f: f.write("") # Add it to git await self.git_run(["add", test_file_path]) # Commit it await self.git_run( ["commit", "-m", "Add empty file for markdown code block test"] ) # User prompt with Markdown code block user_prompt_with_code_block = """Please create a file with this Python code: ``` --- description: Description of when the rule is useful for the LLM globs: *.js,*.ts alwaysApply: false --- Markdown to send to LLM ``` And make sure it runs correctly.""" async with self.create_client_session() as session: # Initialize project to get chat_id init_result_text = await self.call_tool_assert_success( session, "codemcp", { "subtool": "InitProject", "path": self.temp_dir.name, "user_prompt": user_prompt_with_code_block, "subject_line": "test: user prompt with markdown code block", "reuse_head_chat_id": False, }, ) # Extract chat_id from the init result chat_id = self.extract_chat_id_from_text(init_result_text) # Call the WriteFile tool with chat_id result_text = await self.call_tool_assert_success( session, "codemcp", { "subtool": "WriteFile", "path": test_file_path, "content": content, "description": "Write file from prompt with code block", "chat_id": chat_id, }, ) # Verify the success message self.assertIn("Successfully wrote to", result_text) # Verify the file was created with the correct content with open(test_file_path) as f: file_content = f.read() self.assertEqual(file_content, content + "\n") # Get the commit message of the HEAD commit commit_message = await self.git_run( ["log", "-1", "--pretty=%B"], capture_output=True, text=True ) # Normalize the chat_id in the commit message for expect test normalized_commit_message = commit_message.replace(chat_id, "test-chat-id") # Verify that the commit message contains the code block with triple backticks self.assertExpectedInline( normalized_commit_message, """\ test: user prompt with markdown code block Please create a file with this Python code: ``` --- description: Description of when the rule is useful for the LLM globs: *.js,*.ts alwaysApply: false --- Markdown to send to LLM ``` And make sure it runs correctly. ```git-revs 6350984 (Base revision) HEAD Write file from prompt with code block ``` codemcp-id: test-chat-id""", ) # Now do a second write operation with the same chat_id updated_content = content + "\nSecond write with code block in user_prompt" # Call the WriteFile tool again with updated content result_text = await self.call_tool_assert_success( session, "codemcp", { "subtool": "WriteFile", "path": test_file_path, "content": updated_content, "description": "Update file with second write", "chat_id": chat_id, }, ) # Verify the success message self.assertIn("Successfully wrote to", result_text) # Verify the file was updated with the correct content with open(test_file_path) as f: file_content = f.read() self.assertEqual(file_content, updated_content + "\n") # Get the commit message after second write commit_message = await self.git_run( ["log", "-1", "--pretty=%B"], capture_output=True, text=True ) # Normalize the chat_id for expect test normalized_commit_message = commit_message.replace(chat_id, "test-chat-id") # Verify that the commit message still contains the code block with triple backticks self.assertExpectedInline( normalized_commit_message, """\ test: user prompt with markdown code block Please create a file with this Python code: ``` --- description: Description of when the rule is useful for the LLM globs: *.js,*.ts alwaysApply: false --- Markdown to send to LLM ``` And make sure it runs correctly. ```git-revs 6350984 (Base revision) 52d0290 Write file from prompt with code block HEAD Update file with second write ``` codemcp-id: test-chat-id""", ) class OutOfProcessWriteFileTest(WriteFileTest): in_process = False if __name__ == "__main__": unittest.main()

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/ezyang/codemcp'

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