permission_test.go

 1package model
 2
 3import (
 4	"testing"
 5
 6	"github.com/charmbracelet/crush/internal/permission"
 7	"github.com/charmbracelet/crush/internal/ui/dialog"
 8	"github.com/stretchr/testify/require"
 9)
10
11// newTestUIForPermissions builds a UI with a chat, dialog overlay, and
12// common context sufficient to exercise handlePermissionNotification.
13func newTestUIForPermissions() *UI {
14	u := newTestUI()
15	u.dialog = dialog.NewOverlay()
16	return u
17}
18
19func TestHandlePermissionNotification_RemoteGrantClosesDialog(t *testing.T) {
20	t.Parallel()
21
22	u := newTestUIForPermissions()
23	perm := permission.PermissionRequest{
24		ID:         "perm-1",
25		ToolCallID: "tool-call-X",
26		ToolName:   "bash",
27	}
28	u.dialog.OpenDialogWithGrace(dialog.NewPermissions(u.com, perm))
29	require.True(t, u.dialog.ContainsDialog(dialog.PermissionsID))
30
31	u.handlePermissionNotification(permission.PermissionNotification{
32		ToolCallID: "tool-call-X",
33		Granted:    true,
34	})
35
36	require.False(t, u.dialog.ContainsDialog(dialog.PermissionsID),
37		"granted notification should close matching permissions dialog")
38}
39
40func TestHandlePermissionNotification_RemoteDenyClosesDialog(t *testing.T) {
41	t.Parallel()
42
43	u := newTestUIForPermissions()
44	perm := permission.PermissionRequest{
45		ID:         "perm-2",
46		ToolCallID: "tool-call-Y",
47	}
48	u.dialog.OpenDialogWithGrace(dialog.NewPermissions(u.com, perm))
49
50	u.handlePermissionNotification(permission.PermissionNotification{
51		ToolCallID: "tool-call-Y",
52		Denied:     true,
53	})
54
55	require.False(t, u.dialog.ContainsDialog(dialog.PermissionsID),
56		"denied notification should close matching permissions dialog")
57}
58
59func TestHandlePermissionNotification_InitialPendingDoesNotClose(t *testing.T) {
60	t.Parallel()
61
62	u := newTestUIForPermissions()
63	perm := permission.PermissionRequest{
64		ID:         "perm-3",
65		ToolCallID: "tool-call-Z",
66	}
67	u.dialog.OpenDialogWithGrace(dialog.NewPermissions(u.com, perm))
68
69	// The initial notification published by permission.Request is
70	// neither granted nor denied; it must not dismiss the dialog.
71	u.handlePermissionNotification(permission.PermissionNotification{
72		ToolCallID: "tool-call-Z",
73	})
74
75	require.True(t, u.dialog.ContainsDialog(dialog.PermissionsID),
76		"initial pending notification must not close the dialog")
77}
78
79func TestHandlePermissionNotification_DifferentToolCallIDDoesNotClose(t *testing.T) {
80	t.Parallel()
81
82	u := newTestUIForPermissions()
83	perm := permission.PermissionRequest{
84		ID:         "perm-4",
85		ToolCallID: "tool-call-A",
86	}
87	u.dialog.OpenDialogWithGrace(dialog.NewPermissions(u.com, perm))
88
89	u.handlePermissionNotification(permission.PermissionNotification{
90		ToolCallID: "tool-call-B",
91		Granted:    true,
92	})
93
94	require.True(t, u.dialog.ContainsDialog(dialog.PermissionsID),
95		"notification for unrelated tool call must not close the dialog")
96}