@@ -0,0 +1,96 @@
1+package model
2+
3+import (
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.
13+func newTestUIForPermissions() *UI {
14+ u := newTestUI()
15+ u.dialog = dialog.NewOverlay()
16+ return u
17+}
18+
19+func 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.OpenDialog(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+
40+func 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.OpenDialog(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+
59+func 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.OpenDialog(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+
79+func 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.OpenDialog(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+}