102 lines
3.7 KiB
Go
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)
|
|
}
|
|
}
|