package setup import ( "encoding/json" "os" "path/filepath" "testing" ) func TestAddHookCommand(t *testing.T) { tests := []struct { name string existingHooks map[string]interface{} event string command string wantAdded bool }{ { name: "add hook to empty hooks", existingHooks: make(map[string]interface{}), event: "SessionStart", command: "bd prime", wantAdded: true, }, { name: "hook already exists", existingHooks: map[string]interface{}{ "SessionStart": []interface{}{ map[string]interface{}{ "matcher": "", "hooks": []interface{}{ map[string]interface{}{ "type": "command", "command": "bd prime", }, }, }, }, }, event: "SessionStart", command: "bd prime", wantAdded: false, }, { name: "add second hook alongside existing", existingHooks: map[string]interface{}{ "SessionStart": []interface{}{ map[string]interface{}{ "matcher": "", "hooks": []interface{}{ map[string]interface{}{ "type": "command", "command": "other command", }, }, }, }, }, event: "SessionStart", command: "bd prime", wantAdded: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := addHookCommand(tt.existingHooks, tt.event, tt.command) if got != tt.wantAdded { t.Errorf("addHookCommand() = %v, want %v", got, tt.wantAdded) } // Verify hook exists in structure eventHooks, ok := tt.existingHooks[tt.event].([]interface{}) if !ok { t.Fatal("Event hooks not found") } found := false for _, hook := range eventHooks { hookMap := hook.(map[string]interface{}) commands := hookMap["hooks"].([]interface{}) for _, cmd := range commands { cmdMap := cmd.(map[string]interface{}) if cmdMap["command"] == tt.command { found = true break } } } if !found { t.Errorf("Hook command %q not found in event %q", tt.command, tt.event) } }) } } func TestRemoveHookCommand(t *testing.T) { tests := []struct { name string existingHooks map[string]interface{} event string command string wantRemaining int }{ { name: "remove only hook", existingHooks: map[string]interface{}{ "SessionStart": []interface{}{ map[string]interface{}{ "matcher": "", "hooks": []interface{}{ map[string]interface{}{ "type": "command", "command": "bd prime", }, }, }, }, }, event: "SessionStart", command: "bd prime", wantRemaining: 0, }, { name: "remove one of multiple hooks", existingHooks: map[string]interface{}{ "SessionStart": []interface{}{ map[string]interface{}{ "matcher": "", "hooks": []interface{}{ map[string]interface{}{ "type": "command", "command": "other command", }, }, }, map[string]interface{}{ "matcher": "", "hooks": []interface{}{ map[string]interface{}{ "type": "command", "command": "bd prime", }, }, }, }, }, event: "SessionStart", command: "bd prime", wantRemaining: 1, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { removeHookCommand(tt.existingHooks, tt.event, tt.command) eventHooks, ok := tt.existingHooks[tt.event].([]interface{}) if !ok && tt.wantRemaining > 0 { t.Fatal("Event hooks not found") } if len(eventHooks) != tt.wantRemaining { t.Errorf("Expected %d remaining hooks, got %d", tt.wantRemaining, len(eventHooks)) } // Verify target hook is actually gone for _, hook := range eventHooks { hookMap := hook.(map[string]interface{}) commands := hookMap["hooks"].([]interface{}) for _, cmd := range commands { cmdMap := cmd.(map[string]interface{}) if cmdMap["command"] == tt.command { t.Errorf("Hook command %q still present after removal", tt.command) } } } }) } } func TestHasBeadsHooks(t *testing.T) { tmpDir := t.TempDir() tests := []struct { name string settingsData map[string]interface{} want bool }{ { name: "has bd prime hook", settingsData: map[string]interface{}{ "hooks": map[string]interface{}{ "SessionStart": []interface{}{ map[string]interface{}{ "matcher": "", "hooks": []interface{}{ map[string]interface{}{ "type": "command", "command": "bd prime", }, }, }, }, }, }, want: true, }, { name: "no hooks", settingsData: map[string]interface{}{}, want: false, }, { name: "has other hooks but not bd prime", settingsData: map[string]interface{}{ "hooks": map[string]interface{}{ "SessionStart": []interface{}{ map[string]interface{}{ "matcher": "", "hooks": []interface{}{ map[string]interface{}{ "type": "command", "command": "other command", }, }, }, }, }, }, want: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { settingsPath := filepath.Join(tmpDir, "settings.json") data, err := json.Marshal(tt.settingsData) if err != nil { t.Fatalf("Failed to marshal test data: %v", err) } if err := os.WriteFile(settingsPath, data, 0644); err != nil { t.Fatalf("Failed to write test file: %v", err) } got := hasBeadsHooks(settingsPath) if got != tt.want { t.Errorf("hasBeadsHooks() = %v, want %v", got, tt.want) } }) } } func TestIdempotency(t *testing.T) { // Test that running addHookCommand twice doesn't duplicate hooks hooks := make(map[string]interface{}) // First add added1 := addHookCommand(hooks, "SessionStart", "bd prime") if !added1 { t.Error("First call should have added the hook") } // Second add (should detect existing) added2 := addHookCommand(hooks, "SessionStart", "bd prime") if added2 { t.Error("Second call should have detected existing hook") } // Verify only one hook exists eventHooks := hooks["SessionStart"].([]interface{}) if len(eventHooks) != 1 { t.Errorf("Expected 1 hook, got %d", len(eventHooks)) } }