Files
BlackBean/pigo/internal/hooks/dispatch_test.go
T
2026-08-14 23:41:57 +08:00

113 lines
3.0 KiB
Go

package hooks
import (
"bytes"
"context"
"runtime"
"strings"
"testing"
)
func TestNewDispatcherNilOnEmpty(t *testing.T) {
if d := NewDispatcher(nil, "/tmp", nil); d != nil {
t.Fatal("expected nil dispatcher for empty set")
}
if d := NewDispatcher(HookSet{}, "/tmp", nil); d != nil {
t.Fatal("expected nil dispatcher for empty set")
}
}
func TestNilDispatcherDispatchIsNoOp(t *testing.T) {
var d *Dispatcher
dec := d.Dispatch(context.Background(), "PreToolUse", "bash", HookInput{})
if dec.Block || dec.AdditionalContext != "" || dec.UpdatedInput != nil {
t.Fatalf("expected empty decision, got %+v", dec)
}
}
func TestDispatchMergesContext(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("sh -c not available on windows")
}
set := HookSet{
"PostToolUse": {
{Matcher: "*", Hooks: []HookConfig{
{Command: `echo '{"additionalContext":"first"}'`},
{Command: `echo '{"additionalContext":"second"}'`},
}},
},
}
d := NewDispatcher(set, t.TempDir(), nil)
dec := d.Dispatch(context.Background(), "PostToolUse", "write", HookInput{})
if dec.AdditionalContext != "first\nsecond" {
t.Fatalf("expected merged context, got %q", dec.AdditionalContext)
}
}
func TestDispatchPreToolUseFirstBlockShortCircuits(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("sh -c not available on windows")
}
set := HookSet{
"PreToolUse": {
{Matcher: "*", Hooks: []HookConfig{
{Command: `echo "stop" >&2; exit 2`},
{Command: `echo '{"updatedInput":{"changed":true}}'`},
}},
},
}
d := NewDispatcher(set, t.TempDir(), nil)
dec := d.Dispatch(context.Background(), "PreToolUse", "bash", HookInput{})
if !dec.Block {
t.Fatal("expected block")
}
if dec.UpdatedInput != nil {
t.Fatalf("expected short-circuit before rewrite, got %s", dec.UpdatedInput)
}
}
func TestDispatchFailOpen(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("sh -c not available on windows")
}
set := HookSet{
"PreToolUse": {
{Matcher: "*", Hooks: []HookConfig{
{Command: `exit 1`},
{Command: `echo '{"additionalContext":"survived"}'`},
}},
},
}
var warn bytes.Buffer
d := NewDispatcher(set, t.TempDir(), &warn)
dec := d.Dispatch(context.Background(), "PreToolUse", "bash", HookInput{})
if dec.Block {
t.Fatal("failed hook must not block (fail-open)")
}
if dec.AdditionalContext != "survived" {
t.Fatalf("expected later hook to still run, got %q", dec.AdditionalContext)
}
if !strings.Contains(warn.String(), "PreToolUse") {
t.Fatalf("expected failure warning, got %q", warn.String())
}
}
func TestDispatchUpdatedInputLastWins(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("sh -c not available on windows")
}
set := HookSet{
"PreToolUse": {
{Matcher: "*", Hooks: []HookConfig{
{Command: `echo '{"updatedInput":{"n":1}}'`},
{Command: `echo '{"updatedInput":{"n":2}}'`},
}},
},
}
d := NewDispatcher(set, t.TempDir(), nil)
dec := d.Dispatch(context.Background(), "PreToolUse", "bash", HookInput{})
if string(dec.UpdatedInput) != `{"n":2}` {
t.Fatalf("expected last writer wins, got %s", dec.UpdatedInput)
}
}