Cletus plugin kit: rules for coding agents, specification, host API, style guide, worker SDK and cletus-plugin tool
This commit is contained in:
commit
9b19d3a8be
41 files changed
+4121
No files matched your search
@@ -0,0 +1,101 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user