mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-21 17:38:10 +00:00
- Six-phase plan complete: webui/cli/healthcheck/pluginmgr/clawhubadapter now interact with the kernel exclusively via internal/sdk interfaces; all Configure() calls and package-level global injection removed - buildSDK in internal/plugin/registry.go is the single assembly point - Add internal/sdk/events.go exporting event types/constants - Fix ProviderManager cooldown sharing: LuaAdaptedProvider.Name() now returns the source name instead of lua_<adapter>, so multiple sources sharing an adapter (single script load via shared VM AdapterCache) no longer share failure-cooldown state - Verified: build/vet/tests green, deployed to homeagent.service with full plugin capability testing via local OpenAI-compatible mock
390 lines
8.2 KiB
Go
390 lines
8.2 KiB
Go
package agentcli
|
|
|
|
import (
|
|
"encoding/json"
|
|
"testing"
|
|
"time"
|
|
|
|
sdk "gitcode.com/JianFeeeee/HomeAgent/internal/sdk"
|
|
)
|
|
|
|
type toolCapture struct {
|
|
handlers map[string]sdk.ToolHandler
|
|
defs map[string]sdk.ToolDef
|
|
}
|
|
|
|
func newToolCapture() *toolCapture {
|
|
return &toolCapture{
|
|
handlers: make(map[string]sdk.ToolHandler),
|
|
defs: make(map[string]sdk.ToolDef),
|
|
}
|
|
}
|
|
|
|
func (tc *toolCapture) RegisterTool(name string, def sdk.ToolDef, handler sdk.ToolHandler) error {
|
|
tc.handlers[name] = handler
|
|
tc.defs[name] = def
|
|
return nil
|
|
}
|
|
func (tc *toolCapture) RegisterStage(stage sdk.Stage, handler sdk.StageHandler) {}
|
|
func (tc *toolCapture) RegisterAPI(name string) error { return nil }
|
|
|
|
func setupPlugin() (*Plugin, *toolCapture, error) {
|
|
p := New("agentcli")
|
|
tc := newToolCapture()
|
|
sdk := sdk.New("agentcli", sdk.SDKConfig{
|
|
RegTool: tc.RegisterTool,
|
|
RegStage: tc.RegisterStage,
|
|
RegAPI: tc.RegisterAPI,
|
|
Settings: sdk.NewSettings("agentcli", nil),
|
|
})
|
|
if err := p.Start(sdk); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return p, tc, nil
|
|
}
|
|
|
|
func TestKeyMapping(t *testing.T) {
|
|
tests := []struct {
|
|
key string
|
|
expected []byte
|
|
}{
|
|
{"enter", []byte{0x0D}},
|
|
{"tab", []byte{0x09}},
|
|
{"escape", []byte{0x1B}},
|
|
{"esc", []byte{0x1B}},
|
|
{"backspace", []byte{0x7F}},
|
|
{"delete", []byte{0x1B, 0x5B, 0x33, 0x7E}},
|
|
{"home", []byte{0x1B, 0x5B, 0x48}},
|
|
{"end", []byte{0x1B, 0x5B, 0x46}},
|
|
{"up", []byte{0x1B, 0x5B, 0x41}},
|
|
{"down", []byte{0x1B, 0x5B, 0x42}},
|
|
{"left", []byte{0x1B, 0x5B, 0x44}},
|
|
{"right", []byte{0x1B, 0x5B, 0x43}},
|
|
{"page_up", []byte{0x1B, 0x5B, 0x35, 0x7E}},
|
|
{"page_down", []byte{0x1B, 0x5B, 0x36, 0x7E}},
|
|
{"ctrl_a", []byte{0x01}},
|
|
{"ctrl_z", []byte{0x1A}},
|
|
{"alt_a", []byte{0x1B, 'a'}},
|
|
{"alt_z", []byte{0x1B, 'z'}},
|
|
{"f1", []byte{0x1B, 0x5B, 0x50}},
|
|
{"f4", []byte{0x1B, 0x5B, 0x53}},
|
|
{"f12", []byte{0x1B, 0x5B, 0x32, 0x34, 0x7E}},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.key, func(t *testing.T) {
|
|
result, err := mapKey(tt.key)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if !bytesEqual(result, tt.expected) {
|
|
t.Fatalf("expected %v, got %v", tt.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestKeyMappingInvalid(t *testing.T) {
|
|
_, err := mapKey("unknown_key")
|
|
if err == nil {
|
|
t.Fatal("expected error for unknown key")
|
|
}
|
|
}
|
|
|
|
func bytesEqual(a, b []byte) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i := range a {
|
|
if a[i] != b[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func TestListEmpty(t *testing.T) {
|
|
p, tc, err := setupPlugin()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer p.Stop()
|
|
|
|
handler := tc.handlers["terminal_list"]
|
|
result, err := handler(map[string]interface{}{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
data, _ := json.Marshal(result)
|
|
var resp map[string]interface{}
|
|
json.Unmarshal(data, &resp)
|
|
|
|
if resp["status"] != "ok" {
|
|
t.Fatalf("expected status ok, got %v", resp["status"])
|
|
}
|
|
if resp["count"].(float64) != 0 {
|
|
t.Fatalf("expected count 0, got %v", resp["count"])
|
|
}
|
|
}
|
|
|
|
func TestCreateAndCloseTerminal(t *testing.T) {
|
|
p, tc, err := setupPlugin()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer p.Stop()
|
|
|
|
createHandler := tc.handlers["terminal_create"]
|
|
result, err := createHandler(map[string]interface{}{
|
|
"command": "echo hello",
|
|
"timeout": "10s",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
data, _ := json.Marshal(result)
|
|
var createResp map[string]interface{}
|
|
json.Unmarshal(data, &createResp)
|
|
|
|
if v, ok := createResp["error"]; ok {
|
|
t.Skipf("PTY not available in this environment: %v", v)
|
|
}
|
|
if createResp["status"] != "created" {
|
|
t.Fatalf("expected status created, got %v", createResp["status"])
|
|
}
|
|
|
|
id := createResp["id"].(string)
|
|
if id == "" {
|
|
t.Fatal("expected non-empty terminal id")
|
|
}
|
|
|
|
// Give the terminal a moment to output
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
// Read output
|
|
readHandler := tc.handlers["terminal_read"]
|
|
result, err = readHandler(map[string]interface{}{
|
|
"id": id,
|
|
"clear": true,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
data, _ = json.Marshal(result)
|
|
var readResp map[string]interface{}
|
|
json.Unmarshal(data, &readResp)
|
|
if readResp["status"] != "ok" {
|
|
t.Fatalf("expected status ok, got %v", readResp["status"])
|
|
}
|
|
|
|
// Close
|
|
closeHandler := tc.handlers["terminal_close"]
|
|
result, err = closeHandler(map[string]interface{}{
|
|
"id": id,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
data, _ = json.Marshal(result)
|
|
var closeResp map[string]interface{}
|
|
json.Unmarshal(data, &closeResp)
|
|
if closeResp["status"] != "closed" {
|
|
t.Fatalf("expected status closed, got %v", closeResp["status"])
|
|
}
|
|
}
|
|
|
|
func TestCreateTerminalMissingArgs(t *testing.T) {
|
|
p, tc, err := setupPlugin()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer p.Stop()
|
|
|
|
handler := tc.handlers["terminal_create"]
|
|
result, err := handler(map[string]interface{}{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
data, _ := json.Marshal(result)
|
|
var resp map[string]interface{}
|
|
json.Unmarshal(data, &resp)
|
|
|
|
if v, ok := resp["error"]; ok {
|
|
t.Skipf("PTY not available in this environment: %v", v)
|
|
}
|
|
if resp["status"] != "created" {
|
|
t.Fatalf("expected status created, got %v", resp["status"])
|
|
}
|
|
id := resp["id"].(string)
|
|
p.handleClose(map[string]interface{}{"id": id})
|
|
}
|
|
|
|
func TestWriteToNonexistentTerminal(t *testing.T) {
|
|
_, tc, err := setupPlugin()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
handler := tc.handlers["terminal_write"]
|
|
result, err := handler(map[string]interface{}{
|
|
"id": "nonexistent",
|
|
"input": "test",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
data, _ := json.Marshal(result)
|
|
var resp map[string]interface{}
|
|
json.Unmarshal(data, &resp)
|
|
|
|
if _, ok := resp["error"]; !ok {
|
|
t.Fatal("expected error for nonexistent terminal")
|
|
}
|
|
}
|
|
|
|
func TestReadNonexistentTerminal(t *testing.T) {
|
|
_, tc, err := setupPlugin()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
handler := tc.handlers["terminal_read"]
|
|
result, err := handler(map[string]interface{}{
|
|
"id": "nonexistent",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
data, _ := json.Marshal(result)
|
|
var resp map[string]interface{}
|
|
json.Unmarshal(data, &resp)
|
|
|
|
if _, ok := resp["error"]; !ok {
|
|
t.Fatal("expected error for nonexistent terminal")
|
|
}
|
|
}
|
|
|
|
func TestResizeNonexistentTerminal(t *testing.T) {
|
|
_, tc, err := setupPlugin()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
handler := tc.handlers["terminal_resize"]
|
|
result, err := handler(map[string]interface{}{
|
|
"id": "nonexistent",
|
|
"rows": float64(40),
|
|
"cols": float64(120),
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
data, _ := json.Marshal(result)
|
|
var resp map[string]interface{}
|
|
json.Unmarshal(data, &resp)
|
|
|
|
if _, ok := resp["error"]; !ok {
|
|
t.Fatal("expected error for nonexistent terminal")
|
|
}
|
|
}
|
|
|
|
func TestCloseNonexistentTerminal(t *testing.T) {
|
|
_, tc, err := setupPlugin()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
handler := tc.handlers["terminal_close"]
|
|
result, err := handler(map[string]interface{}{
|
|
"id": "nonexistent",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
data, _ := json.Marshal(result)
|
|
var resp map[string]interface{}
|
|
json.Unmarshal(data, &resp)
|
|
|
|
if _, ok := resp["error"]; !ok {
|
|
t.Fatal("expected error for nonexistent terminal")
|
|
}
|
|
}
|
|
|
|
func TestTerminalWriteRequiresId(t *testing.T) {
|
|
_, tc, err := setupPlugin()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
handler := tc.handlers["terminal_write"]
|
|
result, err := handler(map[string]interface{}{
|
|
"input": "test",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
data, _ := json.Marshal(result)
|
|
var resp map[string]interface{}
|
|
json.Unmarshal(data, &resp)
|
|
|
|
if _, ok := resp["error"]; !ok {
|
|
t.Fatal("expected error when id is missing")
|
|
}
|
|
}
|
|
|
|
func TestTerminalWriteRequiresContent(t *testing.T) {
|
|
_, tc, err := setupPlugin()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
handler := tc.handlers["terminal_write"]
|
|
result, err := handler(map[string]interface{}{
|
|
"id": "test",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
data, _ := json.Marshal(result)
|
|
var resp map[string]interface{}
|
|
json.Unmarshal(data, &resp)
|
|
|
|
if _, ok := resp["error"]; !ok {
|
|
t.Fatal("expected error when both input and key are missing")
|
|
}
|
|
}
|
|
|
|
func TestToolsRegistered(t *testing.T) {
|
|
p, tc, err := setupPlugin()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer p.Stop()
|
|
|
|
expectedTools := []string{
|
|
"terminal_create",
|
|
"terminal_write",
|
|
"terminal_read",
|
|
"terminal_resize",
|
|
"terminal_close",
|
|
"terminal_list",
|
|
}
|
|
|
|
for _, name := range expectedTools {
|
|
if _, ok := tc.handlers[name]; !ok {
|
|
t.Errorf("tool %s not registered", name)
|
|
}
|
|
}
|
|
}
|