113 lines
3.0 KiB
Go
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)
|
|
}
|
|
}
|