| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307 |
- package tools
- import (
- "context"
- "encoding/json"
- "os"
- "path/filepath"
- "testing"
- "time"
- "github.com/kujtimiihoxha/termai/internal/lsp"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- )
- func TestWriteTool_Info(t *testing.T) {
- tool := NewWriteTool(make(map[string]*lsp.Client), newMockPermissionService(true))
- info := tool.Info()
- assert.Equal(t, WriteToolName, info.Name)
- assert.NotEmpty(t, info.Description)
- assert.Contains(t, info.Parameters, "file_path")
- assert.Contains(t, info.Parameters, "content")
- assert.Contains(t, info.Required, "file_path")
- assert.Contains(t, info.Required, "content")
- }
- func TestWriteTool_Run(t *testing.T) {
- // Create a temporary directory for testing
- tempDir, err := os.MkdirTemp("", "write_tool_test")
- require.NoError(t, err)
- defer os.RemoveAll(tempDir)
- t.Run("creates a new file successfully", func(t *testing.T) {
- tool := NewWriteTool(make(map[string]*lsp.Client), newMockPermissionService(true))
- filePath := filepath.Join(tempDir, "new_file.txt")
- content := "This is a test content"
- params := WriteParams{
- FilePath: filePath,
- Content: content,
- }
- paramsJSON, err := json.Marshal(params)
- require.NoError(t, err)
- call := ToolCall{
- Name: WriteToolName,
- Input: string(paramsJSON),
- }
- response, err := tool.Run(context.Background(), call)
- require.NoError(t, err)
- assert.Contains(t, response.Content, "successfully written")
- // Verify file was created with correct content
- fileContent, err := os.ReadFile(filePath)
- require.NoError(t, err)
- assert.Equal(t, content, string(fileContent))
- })
- t.Run("creates file with nested directories", func(t *testing.T) {
- tool := NewWriteTool(make(map[string]*lsp.Client), newMockPermissionService(true))
- filePath := filepath.Join(tempDir, "nested/dirs/new_file.txt")
- content := "Content in nested directory"
- params := WriteParams{
- FilePath: filePath,
- Content: content,
- }
- paramsJSON, err := json.Marshal(params)
- require.NoError(t, err)
- call := ToolCall{
- Name: WriteToolName,
- Input: string(paramsJSON),
- }
- response, err := tool.Run(context.Background(), call)
- require.NoError(t, err)
- assert.Contains(t, response.Content, "successfully written")
- // Verify file was created with correct content
- fileContent, err := os.ReadFile(filePath)
- require.NoError(t, err)
- assert.Equal(t, content, string(fileContent))
- })
- t.Run("updates existing file", func(t *testing.T) {
- tool := NewWriteTool(make(map[string]*lsp.Client), newMockPermissionService(true))
- // Create a file first
- filePath := filepath.Join(tempDir, "existing_file.txt")
- initialContent := "Initial content"
- err := os.WriteFile(filePath, []byte(initialContent), 0o644)
- require.NoError(t, err)
- // Record the file read to avoid modification time check failure
- recordFileRead(filePath)
- // Update the file
- updatedContent := "Updated content"
- params := WriteParams{
- FilePath: filePath,
- Content: updatedContent,
- }
- paramsJSON, err := json.Marshal(params)
- require.NoError(t, err)
- call := ToolCall{
- Name: WriteToolName,
- Input: string(paramsJSON),
- }
- response, err := tool.Run(context.Background(), call)
- require.NoError(t, err)
- assert.Contains(t, response.Content, "successfully written")
- // Verify file was updated with correct content
- fileContent, err := os.ReadFile(filePath)
- require.NoError(t, err)
- assert.Equal(t, updatedContent, string(fileContent))
- })
- t.Run("handles invalid parameters", func(t *testing.T) {
- tool := NewWriteTool(make(map[string]*lsp.Client), newMockPermissionService(true))
- call := ToolCall{
- Name: WriteToolName,
- Input: "invalid json",
- }
- response, err := tool.Run(context.Background(), call)
- require.NoError(t, err)
- assert.Contains(t, response.Content, "error parsing parameters")
- })
- t.Run("handles missing file_path", func(t *testing.T) {
- tool := NewWriteTool(make(map[string]*lsp.Client), newMockPermissionService(true))
- params := WriteParams{
- FilePath: "",
- Content: "Some content",
- }
- paramsJSON, err := json.Marshal(params)
- require.NoError(t, err)
- call := ToolCall{
- Name: WriteToolName,
- Input: string(paramsJSON),
- }
- response, err := tool.Run(context.Background(), call)
- require.NoError(t, err)
- assert.Contains(t, response.Content, "file_path is required")
- })
- t.Run("handles missing content", func(t *testing.T) {
- tool := NewWriteTool(make(map[string]*lsp.Client), newMockPermissionService(true))
- params := WriteParams{
- FilePath: filepath.Join(tempDir, "file.txt"),
- Content: "",
- }
- paramsJSON, err := json.Marshal(params)
- require.NoError(t, err)
- call := ToolCall{
- Name: WriteToolName,
- Input: string(paramsJSON),
- }
- response, err := tool.Run(context.Background(), call)
- require.NoError(t, err)
- assert.Contains(t, response.Content, "content is required")
- })
- t.Run("handles writing to a directory path", func(t *testing.T) {
- tool := NewWriteTool(make(map[string]*lsp.Client), newMockPermissionService(true))
- // Create a directory
- dirPath := filepath.Join(tempDir, "test_dir")
- err := os.Mkdir(dirPath, 0o755)
- require.NoError(t, err)
- params := WriteParams{
- FilePath: dirPath,
- Content: "Some content",
- }
- paramsJSON, err := json.Marshal(params)
- require.NoError(t, err)
- call := ToolCall{
- Name: WriteToolName,
- Input: string(paramsJSON),
- }
- response, err := tool.Run(context.Background(), call)
- require.NoError(t, err)
- assert.Contains(t, response.Content, "Path is a directory")
- })
- t.Run("handles permission denied", func(t *testing.T) {
- tool := NewWriteTool(make(map[string]*lsp.Client), newMockPermissionService(false))
- filePath := filepath.Join(tempDir, "permission_denied.txt")
- params := WriteParams{
- FilePath: filePath,
- Content: "Content that should not be written",
- }
- paramsJSON, err := json.Marshal(params)
- require.NoError(t, err)
- call := ToolCall{
- Name: WriteToolName,
- Input: string(paramsJSON),
- }
- response, err := tool.Run(context.Background(), call)
- require.NoError(t, err)
- assert.Contains(t, response.Content, "Permission denied")
- // Verify file was not created
- _, err = os.Stat(filePath)
- assert.True(t, os.IsNotExist(err))
- })
- t.Run("detects file modified since last read", func(t *testing.T) {
- tool := NewWriteTool(make(map[string]*lsp.Client), newMockPermissionService(true))
- // Create a file
- filePath := filepath.Join(tempDir, "modified_file.txt")
- initialContent := "Initial content"
- err := os.WriteFile(filePath, []byte(initialContent), 0o644)
- require.NoError(t, err)
- // Record an old read time
- fileRecordMutex.Lock()
- fileRecords[filePath] = fileRecord{
- path: filePath,
- readTime: time.Now().Add(-1 * time.Hour),
- }
- fileRecordMutex.Unlock()
- // Try to update the file
- params := WriteParams{
- FilePath: filePath,
- Content: "Updated content",
- }
- paramsJSON, err := json.Marshal(params)
- require.NoError(t, err)
- call := ToolCall{
- Name: WriteToolName,
- Input: string(paramsJSON),
- }
- response, err := tool.Run(context.Background(), call)
- require.NoError(t, err)
- assert.Contains(t, response.Content, "has been modified since it was last read")
- // Verify file was not modified
- fileContent, err := os.ReadFile(filePath)
- require.NoError(t, err)
- assert.Equal(t, initialContent, string(fileContent))
- })
- t.Run("skips writing when content is identical", func(t *testing.T) {
- tool := NewWriteTool(make(map[string]*lsp.Client), newMockPermissionService(true))
- // Create a file
- filePath := filepath.Join(tempDir, "identical_content.txt")
- content := "Content that won't change"
- err := os.WriteFile(filePath, []byte(content), 0o644)
- require.NoError(t, err)
- // Record a read time
- recordFileRead(filePath)
- // Try to write the same content
- params := WriteParams{
- FilePath: filePath,
- Content: content,
- }
- paramsJSON, err := json.Marshal(params)
- require.NoError(t, err)
- call := ToolCall{
- Name: WriteToolName,
- Input: string(paramsJSON),
- }
- response, err := tool.Run(context.Background(), call)
- require.NoError(t, err)
- assert.Contains(t, response.Content, "already contains the exact content")
- })
- }
|