Files
vmux/hook_test.go
Pierre Martin e1b176cf55 feat(03-01): HookEvent struct, processHookEvent mapping, UpdateFromHook, WaitType
- HookEvent struct parses Claude Code hook JSON payload
- processHookEvent maps Notification/Stop/PostToolUse/PreToolUse to State+WaitType
- UpdateFromHook creates new entries and manages WaitingSince transitions
- SessionInfo.WaitType serialized in JSON with omitempty
- 12 tests cover all event mappings, edge cases, and JSON serialization

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 19:40:02 +01:00

228 lines
5.5 KiB
Go

package main
import (
"testing"
)
func TestProcessHookNotificationPermission(t *testing.T) {
d := newTestDaemon(t)
d.processHookEvent(HookEvent{
SessionID: "sess-1",
HookEventName: "Notification",
NotificationType: "permission_prompt",
Cwd: "/home/user/project",
})
list := d.registry.List()
if len(list) != 1 {
t.Fatalf("registry len = %d, want 1", len(list))
}
if list[0].State != "Needs Input" {
t.Errorf("State = %q, want %q", list[0].State, "Needs Input")
}
if list[0].WaitType != "permission" {
t.Errorf("WaitType = %q, want %q", list[0].WaitType, "permission")
}
}
func TestProcessHookNotificationIdle(t *testing.T) {
d := newTestDaemon(t)
d.processHookEvent(HookEvent{
SessionID: "sess-1",
HookEventName: "Notification",
NotificationType: "idle_prompt",
Cwd: "/home/user/project",
})
list := d.registry.List()
if len(list) != 1 {
t.Fatalf("registry len = %d, want 1", len(list))
}
if list[0].State != "Needs Input" {
t.Errorf("State = %q, want %q", list[0].State, "Needs Input")
}
if list[0].WaitType != "idle" {
t.Errorf("WaitType = %q, want %q", list[0].WaitType, "idle")
}
}
func TestProcessHookNotificationUnknown(t *testing.T) {
d := newTestDaemon(t)
d.processHookEvent(HookEvent{
SessionID: "sess-1",
HookEventName: "Notification",
NotificationType: "some_unknown_type",
Cwd: "/home/user/project",
})
list := d.registry.List()
if len(list) != 1 {
t.Fatalf("registry len = %d, want 1", len(list))
}
if list[0].State != "Needs Input" {
t.Errorf("State = %q, want %q", list[0].State, "Needs Input")
}
if list[0].WaitType != "question" {
t.Errorf("WaitType = %q, want %q", list[0].WaitType, "question")
}
}
func TestProcessHookStop(t *testing.T) {
d := newTestDaemon(t)
d.processHookEvent(HookEvent{
SessionID: "sess-1",
HookEventName: "Stop",
Cwd: "/home/user/project",
})
list := d.registry.List()
if len(list) != 1 {
t.Fatalf("registry len = %d, want 1", len(list))
}
if list[0].State != "Needs Input" {
t.Errorf("State = %q, want %q", list[0].State, "Needs Input")
}
if list[0].WaitType != "question" {
t.Errorf("WaitType = %q, want %q", list[0].WaitType, "question")
}
}
func TestProcessHookPostToolUse(t *testing.T) {
d := newTestDaemon(t)
// First set session to Needs Input
d.registry.UpdateFromHook("sess-1", "Needs Input", "permission", "/tmp")
d.processHookEvent(HookEvent{
SessionID: "sess-1",
HookEventName: "PostToolUse",
ToolName: "Bash",
Cwd: "/home/user/project",
})
list := d.registry.List()
if len(list) != 1 {
t.Fatalf("registry len = %d, want 1", len(list))
}
if list[0].State != "Working" {
t.Errorf("State = %q, want %q", list[0].State, "Working")
}
if list[0].WaitType != "" {
t.Errorf("WaitType = %q, want empty", list[0].WaitType)
}
}
func TestProcessHookPreToolUse(t *testing.T) {
d := newTestDaemon(t)
d.processHookEvent(HookEvent{
SessionID: "sess-1",
HookEventName: "PreToolUse",
ToolName: "Read",
Cwd: "/home/user/project",
})
list := d.registry.List()
if len(list) != 1 {
t.Fatalf("registry len = %d, want 1", len(list))
}
if list[0].State != "Working" {
t.Errorf("State = %q, want %q", list[0].State, "Working")
}
if list[0].WaitType != "" {
t.Errorf("WaitType = %q, want empty", list[0].WaitType)
}
}
func TestProcessHookIgnoresEmptySessionID(t *testing.T) {
d := newTestDaemon(t)
d.processHookEvent(HookEvent{
SessionID: "",
HookEventName: "Notification",
})
list := d.registry.List()
if len(list) != 0 {
t.Errorf("registry len = %d, want 0 (empty session_id should be ignored)", len(list))
}
}
func TestProcessHookIgnoresUnknownEvent(t *testing.T) {
d := newTestDaemon(t)
d.processHookEvent(HookEvent{
SessionID: "sess-1",
HookEventName: "SomeUnknownEvent",
})
list := d.registry.List()
if len(list) != 0 {
t.Errorf("registry len = %d, want 0 (unknown event should be ignored)", len(list))
}
}
func TestUpdateFromHookCreatesNewEntry(t *testing.T) {
reg := NewRegistry()
reg.UpdateFromHook("new-sess", "Working", "", "/home/user/project")
list := reg.List()
if len(list) != 1 {
t.Fatalf("registry len = %d, want 1", len(list))
}
if list[0].SessionID != "new-sess" {
t.Errorf("SessionID = %q, want %q", list[0].SessionID, "new-sess")
}
if list[0].State != "Working" {
t.Errorf("State = %q, want %q", list[0].State, "Working")
}
if list[0].Cwd != "/home/user/project" {
t.Errorf("Cwd = %q, want %q", list[0].Cwd, "/home/user/project")
}
}
func TestUpdateFromHookSetsWaitingSince(t *testing.T) {
reg := NewRegistry()
// Start as Working
reg.UpdateFromHook("sess-1", "Working", "", "/tmp")
list := reg.List()
if list[0].WaitingSince != nil {
t.Error("WaitingSince should be nil when Working")
}
// Transition to Needs Input
reg.UpdateFromHook("sess-1", "Needs Input", "permission", "/tmp")
list = reg.List()
if list[0].WaitingSince == nil {
t.Fatal("WaitingSince should be set when transitioning to Needs Input")
}
}
func TestUpdateFromHookClearsWaitingSince(t *testing.T) {
reg := NewRegistry()
// Set up as Needs Input
reg.UpdateFromHook("sess-1", "Needs Input", "permission", "/tmp")
list := reg.List()
if list[0].WaitingSince == nil {
t.Fatal("WaitingSince should be set")
}
// Transition back to Working
reg.UpdateFromHook("sess-1", "Working", "", "/tmp")
list = reg.List()
if list[0].WaitingSince != nil {
t.Errorf("WaitingSince should be nil after returning to Working, got %v", list[0].WaitingSince)
}
}