1package tools
2
3import (
4 "encoding/json"
5 "fmt"
6 "os"
7 "path/filepath"
8 "strings"
9 "testing"
10
11 "github.com/stretchr/testify/require"
12)
13
14func TestReadTextFileBoundaryCases(t *testing.T) {
15 t.Parallel()
16
17 tmpDir := t.TempDir()
18 filePath := filepath.Join(tmpDir, "sample.txt")
19
20 var allLines []string
21 for i := range 5 {
22 allLines = append(allLines, fmt.Sprintf("line %d", i+1))
23 }
24 require.NoError(t, os.WriteFile(filePath, []byte(strings.Join(allLines, "\n")), 0o644))
25
26 tests := []struct {
27 name string
28 offset int
29 limit int
30 wantContent string
31 wantHasMore bool
32 }{
33 {
34 name: "exactly limit lines remaining",
35 offset: 0,
36 limit: 5,
37 wantContent: "line 1\nline 2\nline 3\nline 4\nline 5",
38 wantHasMore: false,
39 },
40 {
41 name: "limit plus one line remaining",
42 offset: 0,
43 limit: 4,
44 wantContent: "line 1\nline 2\nline 3\nline 4",
45 wantHasMore: true,
46 },
47 {
48 name: "offset at last line",
49 offset: 4,
50 limit: 3,
51 wantContent: "line 5",
52 wantHasMore: false,
53 },
54 {
55 name: "offset beyond eof",
56 offset: 10,
57 limit: 3,
58 wantContent: "",
59 wantHasMore: false,
60 },
61 }
62
63 for _, tt := range tests {
64 t.Run(tt.name, func(t *testing.T) {
65 t.Parallel()
66
67 gotContent, gotHasMore, err := readTextFile(filePath, tt.offset, tt.limit)
68 require.NoError(t, err)
69 require.Equal(t, tt.wantContent, gotContent)
70 require.Equal(t, tt.wantHasMore, gotHasMore)
71 })
72 }
73}
74
75func TestReadTextFileTruncatesLongLines(t *testing.T) {
76 t.Parallel()
77
78 tmpDir := t.TempDir()
79 filePath := filepath.Join(tmpDir, "longline.txt")
80
81 longLine := strings.Repeat("a", MaxLineLength+10)
82 require.NoError(t, os.WriteFile(filePath, []byte(longLine), 0o644))
83
84 content, hasMore, err := readTextFile(filePath, 0, 1)
85 require.NoError(t, err)
86 require.False(t, hasMore)
87 require.Equal(t, strings.Repeat("a", MaxLineLength)+"...", content)
88}
89
90func TestReadBuiltinFile(t *testing.T) {
91 t.Parallel()
92
93 t.Run("reads crush-config skill", func(t *testing.T) {
94 t.Parallel()
95
96 resp, err := readBuiltinFile(ViewParams{
97 FilePath: "crush://skills/crush-config/SKILL.md",
98 }, nil)
99 require.NoError(t, err)
100 require.NotEmpty(t, resp.Content)
101 require.Contains(t, resp.Content, "Crush Configuration")
102 })
103
104 t.Run("not found", func(t *testing.T) {
105 t.Parallel()
106
107 resp, err := readBuiltinFile(ViewParams{
108 FilePath: "crush://skills/nonexistent/SKILL.md",
109 }, nil)
110 require.NoError(t, err)
111 require.True(t, resp.IsError)
112 })
113
114 t.Run("metadata has skill info", func(t *testing.T) {
115 t.Parallel()
116
117 resp, err := readBuiltinFile(ViewParams{
118 FilePath: "crush://skills/crush-config/SKILL.md",
119 }, nil)
120 require.NoError(t, err)
121
122 var meta ViewResponseMetadata
123 require.NoError(t, json.Unmarshal([]byte(resp.Metadata), &meta))
124 require.Equal(t, ViewResourceSkill, meta.ResourceType)
125 require.Equal(t, "crush-config", meta.ResourceName)
126 require.NotEmpty(t, meta.ResourceDescription)
127 })
128
129 t.Run("respects offset", func(t *testing.T) {
130 t.Parallel()
131
132 resp, err := readBuiltinFile(ViewParams{
133 FilePath: "crush://skills/crush-config/SKILL.md",
134 Offset: 5,
135 }, nil)
136 require.NoError(t, err)
137 require.NotContains(t, resp.Content, " 1|")
138 })
139}