You've already forked agentic-coding-workflow
feat(acp/handlers): fs/read_text_file + fs/write_text_file with path safety
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/yan1h/agent-coding-workflow/internal/acp/handlers"
|
||||
)
|
||||
|
||||
func TestFsRead_HappyPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := t.TempDir()
|
||||
require.NoError(t, os.WriteFile(filepath.Join(tmp, "a.txt"), []byte("hello\n"), 0o644))
|
||||
|
||||
h := handlers.NewFsReadHandler()
|
||||
sess := handlers.SessionContext{SessionID: uuid.New(), CwdPath: tmp}
|
||||
params := mustJSON(t, map[string]any{"sessionId": "x", "path": filepath.Join(tmp, "a.txt")})
|
||||
|
||||
res := h.Handle(context.Background(), sess, params)
|
||||
require.Nil(t, res.Err)
|
||||
require.NotNil(t, res.OK)
|
||||
var out struct {
|
||||
Content string `json:"content"`
|
||||
}
|
||||
require.NoError(t, json.Unmarshal(res.OK, &out))
|
||||
assert.Equal(t, "hello\n", out.Content)
|
||||
}
|
||||
|
||||
func TestFsRead_PathOutsideWorktree(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := t.TempDir()
|
||||
h := handlers.NewFsReadHandler()
|
||||
sess := handlers.SessionContext{CwdPath: tmp}
|
||||
outside := filepath.Join(filepath.VolumeName(tmp)+string(filepath.Separator), "etc", "passwd")
|
||||
params := mustJSON(t, map[string]any{"sessionId": "x", "path": outside})
|
||||
|
||||
res := h.Handle(context.Background(), sess, params)
|
||||
require.NotNil(t, res.Err)
|
||||
assert.Equal(t, -32001, res.Err.Code)
|
||||
}
|
||||
|
||||
func TestFsRead_FileNotFound(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := t.TempDir()
|
||||
h := handlers.NewFsReadHandler()
|
||||
sess := handlers.SessionContext{CwdPath: tmp}
|
||||
params := mustJSON(t, map[string]any{"sessionId": "x", "path": filepath.Join(tmp, "nope.txt")})
|
||||
|
||||
res := h.Handle(context.Background(), sess, params)
|
||||
require.NotNil(t, res.Err)
|
||||
assert.Equal(t, -32603, res.Err.Code)
|
||||
}
|
||||
|
||||
func TestFsRead_LineLimit(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := t.TempDir()
|
||||
require.NoError(t, os.WriteFile(filepath.Join(tmp, "f.txt"),
|
||||
[]byte("L0\nL1\nL2\nL3\n"), 0o644))
|
||||
|
||||
h := handlers.NewFsReadHandler()
|
||||
sess := handlers.SessionContext{CwdPath: tmp}
|
||||
line := 1
|
||||
limit := 2
|
||||
params := mustJSON(t, map[string]any{
|
||||
"sessionId": "x", "path": filepath.Join(tmp, "f.txt"),
|
||||
"line": line, "limit": limit,
|
||||
})
|
||||
res := h.Handle(context.Background(), sess, params)
|
||||
require.Nil(t, res.Err)
|
||||
var out struct {
|
||||
Content string `json:"content"`
|
||||
}
|
||||
_ = json.Unmarshal(res.OK, &out)
|
||||
assert.Equal(t, "L1\nL2", out.Content)
|
||||
}
|
||||
|
||||
func mustJSON(t *testing.T, v any) json.RawMessage {
|
||||
t.Helper()
|
||||
b, err := json.Marshal(v)
|
||||
require.NoError(t, err)
|
||||
return b
|
||||
}
|
||||
Reference in New Issue
Block a user