1package tools
2
3import (
4 "context"
5 "encoding/json"
6 "os"
7 "path/filepath"
8 "testing"
9 "time"
10
11 "charm.land/fantasy"
12 "github.com/stretchr/testify/require"
13)
14
15type mockFileTrackerService struct{}
16
17func (m mockFileTrackerService) RecordRead(ctx context.Context, sessionID, path string) {}
18
19func (m mockFileTrackerService) LastReadTime(ctx context.Context, sessionID, path string) time.Time {
20 return time.Now()
21}
22
23func (m mockFileTrackerService) ListReadFiles(ctx context.Context, sessionID string) ([]string, error) {
24 return nil, nil
25}
26
27func TestTouchToolCreatesEmptyFile(t *testing.T) {
28 t.Parallel()
29
30 workingDir := t.TempDir()
31 tool := NewTouchTool(nil, &mockPermissionService{}, &mockHistoryService{}, mockFileTrackerService{}, workingDir)
32 ctx := context.WithValue(context.Background(), SessionIDContextKey, "test-session")
33
34 resp := runTouchTool(t, tool, ctx, TouchParams{FilePath: "nested/empty.txt"})
35 require.False(t, resp.IsError)
36
37 filePath := filepath.Join(workingDir, "nested", "empty.txt")
38 info, err := os.Stat(filePath)
39 require.NoError(t, err)
40 require.False(t, info.IsDir())
41 require.Zero(t, info.Size())
42}
43
44func TestTouchToolRefusesExistingFile(t *testing.T) {
45 t.Parallel()
46
47 workingDir := t.TempDir()
48 filePath := filepath.Join(workingDir, "existing.txt")
49 require.NoError(t, os.WriteFile(filePath, []byte("content"), 0o644))
50
51 tool := NewTouchTool(nil, &mockPermissionService{}, &mockHistoryService{}, mockFileTrackerService{}, workingDir)
52 ctx := context.WithValue(context.Background(), SessionIDContextKey, "test-session")
53
54 resp := runTouchTool(t, tool, ctx, TouchParams{FilePath: "existing.txt"})
55 require.True(t, resp.IsError)
56 require.Contains(t, resp.Content, "File already exists")
57
58 content, err := os.ReadFile(filePath)
59 require.NoError(t, err)
60 require.Equal(t, "content", string(content))
61}
62
63func TestWriteToolEmptyContentPointsToTouch(t *testing.T) {
64 t.Parallel()
65
66 tool := NewWriteTool(nil, nil, nil, nil, t.TempDir())
67
68 input, err := json.Marshal(WriteParams{FilePath: "empty.txt"})
69 require.NoError(t, err)
70
71 resp, err := tool.Run(context.Background(), fantasy.ToolCall{
72 ID: "test-call",
73 Name: WriteToolName,
74 Input: string(input),
75 })
76 require.NoError(t, err)
77 require.True(t, resp.IsError)
78 require.Equal(t, `content is required. use the "touch" tool to create an empty file`, resp.Content)
79}
80
81func runTouchTool(t *testing.T, tool fantasy.AgentTool, ctx context.Context, params TouchParams) fantasy.ToolResponse {
82 t.Helper()
83
84 input, err := json.Marshal(params)
85 require.NoError(t, err)
86
87 resp, err := tool.Run(ctx, fantasy.ToolCall{
88 ID: "test-call",
89 Name: TouchToolName,
90 Input: string(input),
91 })
92 require.NoError(t, err)
93 return resp
94}