Files

102 lines
3.7 KiB
Go

package main
import (
"encoding/base64"
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
"git.eidonix.com/eidonix/cletus-plugins/sdk/manifest"
)
func newTestHost(t *testing.T, perms ...string) *testHost {
t.Helper()
root := t.TempDir()
files, ws := filepath.Join(root, "files"), filepath.Join(root, "ws")
for _, d := range []string{files, ws} {
if err := os.MkdirAll(d, 0o755); err != nil {
t.Fatal(err)
}
}
if err := os.WriteFile(filepath.Join(files, "a.txt"), []byte("a"), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(ws, "ref.csv"), []byte("x,y"), 0o644); err != nil {
t.Fatal(err)
}
return &testHost{plugin: &manifest.Plugin{ID: "acme.demo", Permissions: perms}, filesDir: files, workspaceDir: ws,
dataDir: filepath.Join(root, "data"), capabilities: map[string]bool{}}
}
func ask(t *testing.T, h *testHost, method string, params any) (any, error) {
t.Helper()
raw, _ := json.Marshal(params)
return h.answer(method, raw)
}
func TestPermissionsGateHostCalls(t *testing.T) {
h := newTestHost(t)
_, err := ask(t, h, "host.files.resolve", map[string]string{"path": "a.txt"})
if err == nil || !strings.Contains(err.Error(), "needs the files permission") {
t.Fatalf("undeclared files call = %v", err)
}
if _, err := ask(t, h, "host.code.llm.start", nil); err == nil || !strings.Contains(err.Error(), "unknown plugin host operation") {
t.Fatalf("first-party call = %v", err)
}
if _, err := ask(t, h, "host.event", map[string]any{"name": "other:x"}); err == nil {
t.Fatal("an event outside the plugin's prefix was allowed")
}
if _, err := ask(t, h, "host.event", map[string]any{"name": "acme.demo:x"}); err != nil {
t.Fatalf("own event = %v", err)
}
}
func TestFilesStayInsideTheOpenedFolder(t *testing.T) {
h := newTestHost(t, "files")
got, err := ask(t, h, "host.files.resolve", map[string]string{"path": "a.txt"})
if err != nil || got != filepath.Join(h.filesDir, "a.txt") {
t.Fatalf("resolve = %v, %v", got, err)
}
for _, path := range []string{"../escape.txt", "/etc/hosts"} {
if _, err := ask(t, h, "host.files.resolve", map[string]string{"path": path}); err == nil {
t.Errorf("resolve(%s) was allowed", path)
}
}
if got, _ := ask(t, h, "host.files.can_write", map[string]string{"path": "new.txt"}); got != false {
t.Fatal("can_write without -edit should be false")
}
h.edit = true
if got, _ := ask(t, h, "host.files.can_write", map[string]string{"path": "new.txt"}); got != true {
t.Fatal("can_write with -edit should be true")
}
}
func TestWorkspaceSimulation(t *testing.T) {
h := newTestHost(t, "files", "workspace.read", "workspace.write")
listed, err := ask(t, h, "host.workspace.list", map[string]string{"workspace": testWorkspaceName})
if err != nil || len(listed.([]map[string]any)) != 1 {
t.Fatalf("list = %v, %v", listed, err)
}
key := testWorkspaceID + "/files/ref.csv"
fetched, err := ask(t, h, "host.workspace.fetch", map[string]string{"key": key})
if err != nil {
t.Fatal(err)
}
path := fetched.(map[string]string)["path"]
if _, err := ask(t, h, "host.files.resolve", map[string]string{"path": path}); err != nil {
t.Fatalf("a fetched copy should resolve: %v", err)
}
data := base64.StdEncoding.EncodeToString([]byte("new"))
if _, err := ask(t, h, "host.workspace.put", map[string]string{"workspace": testWorkspaceID, "key": "out/r.txt", "data": data}); err != nil {
t.Fatal(err)
}
if _, err := ask(t, h, "host.workspace.put", map[string]string{"workspace": testWorkspaceID, "key": "../x", "data": data}); err == nil {
t.Fatal("a key with .. was accepted")
}
if _, err := ask(t, h, "host.workspace.read", map[string]string{"key": testWorkspaceID + "/files/out/r.txt"}); err != nil {
t.Fatal(err)
}
}