// hooks_test.go -- Hook 管理器的单元测试. // // 覆盖场景: // - Register 注册 hook // - 无效 hook 类型报错 // - 空命令报错 // - HasHooks 检查 // - Execute 同步执行 // - ExecuteAsync 异步执行 // - Disable/Enable 全局禁用 // - Count 计数 // - Unregister 注销 package hooks import ( "context" "testing" "git.flytoex.net/yuanwei/flyto-agent/pkg/execenv" ) // TestManager_Register 测试注册 hook func TestManager_Register(t *testing.T) { m := NewManager(nil, execenv.DefaultExecutor{}) err := m.Register(HookPreToolUse, HookDef{Command: "echo test"}) if err != nil { t.Fatalf("注册失败: %v", err) } if m.Count(HookPreToolUse) != 1 { t.Errorf("注册后数量应为 1, 实际: %d", m.Count(HookPreToolUse)) } } // TestManager_RegisterInvalidType 测试无效 hook 类型 func TestManager_RegisterInvalidType(t *testing.T) { m := NewManager(nil, execenv.DefaultExecutor{}) err := m.Register("invalid_type", HookDef{Command: "echo test"}) if err == nil { t.Error("无效 hook 类型应报错") } } // TestManager_RegisterEmptyCommand 测试空命令报错 func TestManager_RegisterEmptyCommand(t *testing.T) { m := NewManager(nil, execenv.DefaultExecutor{}) err := m.Register(HookPreToolUse, HookDef{Command: ""}) if err == nil { t.Error("空命令应报错") } } // TestManager_HasHooks 测试检查是否有 hook func TestManager_HasHooks(t *testing.T) { m := NewManager(nil, execenv.DefaultExecutor{}) if m.HasHooks(HookPreToolUse) { t.Error("未注册时应返回 false") } m.Register(HookPreToolUse, HookDef{Command: "echo test"}) if !m.HasHooks(HookPreToolUse) { t.Error("注册后应返回 true") } if m.HasHooks(HookPostToolUse) { t.Error("其他类型应返回 false") } } // TestManager_Execute 测试同步执行 func TestManager_Execute(t *testing.T) { m := NewManager(nil, execenv.DefaultExecutor{}) m.Register(HookPreToolUse, HookDef{Command: "echo hello"}) results, err := m.Execute(context.Background(), HookPreToolUse, nil) if err != nil { t.Fatalf("执行失败: %v", err) } if len(results.Results) != 1 { t.Fatalf("期望 1 个结果, 实际 %d", len(results.Results)) } if !results.Results[0].Success() { t.Errorf("命令应成功: %v", results.Results[0].Error) } } // TestManager_Execute_NoHooks 测试无 hook 时执行返回空 func TestManager_Execute_NoHooks(t *testing.T) { m := NewManager(nil, execenv.DefaultExecutor{}) results, err := m.Execute(context.Background(), HookPreToolUse, nil) if err != nil { t.Fatalf("不应返回错误: %v", err) } if len(results.Results) != 0 { t.Errorf("无 hook 应返回空结果, 实际 %d 个", len(results.Results)) } } // TestManager_ExecuteAsync 测试异步执行 func TestManager_ExecuteAsync(t *testing.T) { m := NewManager(nil, execenv.DefaultExecutor{}) m.Register(HookNotification, HookDef{Command: "echo async", Async: true}) ch := m.ExecuteAsync(HookNotification, nil) results := <-ch if len(results.Results) != 1 { t.Fatalf("期望 1 个结果, 实际 %d", len(results.Results)) } } // TestManager_Disable 测试全局禁用 func TestManager_Disable(t *testing.T) { m := NewManager(nil, execenv.DefaultExecutor{}) m.Register(HookPreToolUse, HookDef{Command: "echo test"}) m.Disable() results, _ := m.Execute(context.Background(), HookPreToolUse, nil) if len(results.Results) != 0 { t.Error("禁用后应返回空结果") } m.Enable() results, _ = m.Execute(context.Background(), HookPreToolUse, nil) if len(results.Results) != 1 { t.Error("启用后应正常执行") } } // TestManager_Unregister 测试注销所有指定类型的 hook func TestManager_Unregister(t *testing.T) { m := NewManager(nil, execenv.DefaultExecutor{}) m.Register(HookPreToolUse, HookDef{Command: "echo 1"}) m.Register(HookPreToolUse, HookDef{Command: "echo 2"}) m.Unregister(HookPreToolUse) if m.Count(HookPreToolUse) != 0 { t.Errorf("注销后数量应为 0, 实际: %d", m.Count(HookPreToolUse)) } } // TestManager_FromConfig 测试从配置创建 func TestManager_FromConfig(t *testing.T) { cfg := &Config{ Hooks: map[HookType][]HookDef{ HookPreToolUse: {{Command: "echo pre"}}, HookSessionStart: {{Command: "echo start"}}, }, } m := NewManager(cfg, execenv.DefaultExecutor{}) if m.Count(HookPreToolUse) != 1 { t.Errorf("配置加载后 pre_tool_use 数量: %d", m.Count(HookPreToolUse)) } if m.Count(HookSessionStart) != 1 { t.Errorf("配置加载后 session_start 数量: %d", m.Count(HookSessionStart)) } } // TestManager_SkipAsyncInSync 测试同步执行跳过异步 hook func TestManager_SkipAsyncInSync(t *testing.T) { m := NewManager(nil, execenv.DefaultExecutor{}) m.Register(HookPreToolUse, HookDef{Command: "echo sync"}) m.Register(HookPreToolUse, HookDef{Command: "echo async", Async: true}) results, _ := m.Execute(context.Background(), HookPreToolUse, nil) // 同步执行应跳过异步的 if len(results.Results) != 1 { t.Errorf("同步执行应只有 1 个结果(跳过异步), 实际 %d", len(results.Results)) } }