Files
HomeAgent/internal/plugins/agentcli/plugin_test.go
root a62f5ba0fa agentcli: Windows ConPTY 适配分支(原生实现替代剔除桩)+ cabi loader.c build tag 修复
- agentcli 平台无关化:plugin.go 拆为公共层 + ptyTerm 接口,新增
  pty_linux.go(/dev/ptmx 原实现搬移,行为不变)与 pty_windows.go
  (ConPTY 原生实现,CreatePseudoConsole + PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
  注入子进程,双管道读写/ResizePseudoConsole/GetExitCodeProcess,零第三方依赖)
- plugin_stub.go 收紧为 !linux && !windows(darwin 等仍走桩)
- plugin_test.go build tag 放宽到 linux || windows
- cabi/loader.c 补 linux || darwin build tag,修复 Windows 下
  cgo 禁用时残留 C 源文件的编译错误
- 验证:Linux 全量构建+测试通过;Windows CGO 交叉编译 + go vet 全包零错误
2026-08-07 12:32:45 +08:00

392 lines
8.3 KiB
Go

//go:build linux || windows
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)
}
}
}