Files
BlackBean/pigo/internal/cli/run/hooks_install_test.go
T
2026-08-14 23:41:57 +08:00

109 lines
3.9 KiB
Go

package run
import (
"context"
"testing"
"github.com/smallnest/pigo/internal/agentcore"
"github.com/smallnest/pigo/internal/hooks"
"github.com/smallnest/pigo/internal/runtime"
)
func TestInstallHooksEmptyShortCircuits(t *testing.T) {
var cfg runtime.RunConfig
if d := InstallHooks(&cfg, nil, HookDeps{ProjectDir: t.TempDir()}); d != nil {
t.Fatalf("expected nil dispatcher for empty hook set, got %v", d)
}
if d := InstallHooks(&cfg, hooks.HookSet{}, HookDeps{}); d != nil {
t.Fatalf("expected nil dispatcher for empty map, got %v", d)
}
}
func TestInstallHooksBuildsDispatcher(t *testing.T) {
set := hooks.HookSet{
"PreToolUse": {{Matcher: "*", Hooks: []hooks.HookConfig{{Command: "true"}}}},
}
var cfg runtime.RunConfig
d := InstallHooks(&cfg, set, HookDeps{ProjectDir: t.TempDir()})
if d == nil {
t.Fatal("expected non-nil dispatcher for non-empty hook set")
}
}
func TestChainBeforeToolCall(t *testing.T) {
block := &agentcore.BeforeToolCallDecision{Block: true}
allow := func(context.Context, agentcore.AgentToolCall) *agentcore.BeforeToolCallDecision { return nil }
deny := func(context.Context, agentcore.AgentToolCall) *agentcore.BeforeToolCallDecision { return block }
// nil operands act as identity.
if got := chainBeforeToolCall(nil, deny); got == nil {
t.Fatal("nil prev should return next")
}
if got := chainBeforeToolCall(allow, nil); got == nil {
t.Fatal("nil next should return prev")
}
// prev blocks → short-circuit, next never runs.
nextRan := false
spy := func(context.Context, agentcore.AgentToolCall) *agentcore.BeforeToolCallDecision {
nextRan = true
return nil
}
if dec := chainBeforeToolCall(deny, spy)(context.Background(), agentcore.AgentToolCall{}); dec == nil || !dec.Block {
t.Fatalf("expected block decision, got %v", dec)
}
if nextRan {
t.Fatal("next should not run after prev blocks")
}
// prev allows → next runs and decides.
if dec := chainBeforeToolCall(allow, deny)(context.Background(), agentcore.AgentToolCall{}); dec == nil || !dec.Block {
t.Fatalf("expected next's block decision, got %v", dec)
}
}
func TestChainAfterToolCall(t *testing.T) {
content := agentcore.ContentList{}
prevRes := &agentcore.AfterToolCallResult{Content: &content}
nextRes := &agentcore.AfterToolCallResult{Content: &content}
prev := func(context.Context, agentcore.AgentToolCall, agentcore.AgentToolResult, bool) *agentcore.AfterToolCallResult {
return prevRes
}
nilNext := func(context.Context, agentcore.AgentToolCall, agentcore.AgentToolResult, bool) *agentcore.AfterToolCallResult {
return nil
}
next := func(context.Context, agentcore.AgentToolCall, agentcore.AgentToolResult, bool) *agentcore.AfterToolCallResult {
return nextRes
}
// next returns nil → prev's result preserved.
if got := chainAfterToolCall(prev, nilNext)(context.Background(), agentcore.AgentToolCall{}, agentcore.AgentToolResult{}, false); got != prevRes {
t.Fatalf("expected prev result when next is nil, got %v", got)
}
// next returns non-nil → next wins.
if got := chainAfterToolCall(prev, next)(context.Background(), agentcore.AgentToolCall{}, agentcore.AgentToolResult{}, false); got != nextRes {
t.Fatalf("expected next result to win, got %v", got)
}
}
func TestChainShouldStop(t *testing.T) {
yes := func(context.Context, *agentcore.AgentContext) bool { return true }
no := func(context.Context, *agentcore.AgentContext) bool { return false }
if got := chainShouldStop(no, no)(context.Background(), nil); got {
t.Fatal("both false should be false")
}
if got := chainShouldStop(no, yes)(context.Background(), nil); !got {
t.Fatal("next true should stop")
}
// prev true short-circuits without consulting next.
nextRan := false
spy := func(context.Context, *agentcore.AgentContext) bool { nextRan = true; return false }
if got := chainShouldStop(yes, spy)(context.Background(), nil); !got {
t.Fatal("prev true should stop")
}
if nextRan {
t.Fatal("next should not run after prev returns true")
}
}