1package permission
2
3import (
4 "sync"
5 "testing"
6
7 "github.com/stretchr/testify/assert"
8 "github.com/stretchr/testify/require"
9)
10
11func TestPermissionService_AllowedCommands(t *testing.T) {
12 tests := []struct {
13 name string
14 allowedTools []string
15 toolName string
16 action string
17 expected bool
18 }{
19 {
20 name: "tool in allowlist",
21 allowedTools: []string{"bash", "view"},
22 toolName: "bash",
23 action: "execute",
24 expected: true,
25 },
26 {
27 name: "tool:action in allowlist",
28 allowedTools: []string{"bash:execute", "edit:create"},
29 toolName: "bash",
30 action: "execute",
31 expected: true,
32 },
33 {
34 name: "tool not in allowlist",
35 allowedTools: []string{"view", "ls"},
36 toolName: "bash",
37 action: "execute",
38 expected: false,
39 },
40 {
41 name: "tool:action not in allowlist",
42 allowedTools: []string{"bash:read", "edit:create"},
43 toolName: "bash",
44 action: "execute",
45 expected: false,
46 },
47 {
48 name: "empty allowlist",
49 allowedTools: []string{},
50 toolName: "bash",
51 action: "execute",
52 expected: false,
53 },
54 }
55
56 for _, tt := range tests {
57 t.Run(tt.name, func(t *testing.T) {
58 service := NewPermissionService("/tmp", false, tt.allowedTools)
59
60 // Create a channel to capture the permission request
61 // Since we're testing the allowlist logic, we need to simulate the request
62 ps := service.(*permissionService)
63
64 // Test the allowlist logic directly
65 commandKey := tt.toolName + ":" + tt.action
66 allowed := false
67 for _, cmd := range ps.allowedTools {
68 if cmd == commandKey || cmd == tt.toolName {
69 allowed = true
70 break
71 }
72 }
73
74 if allowed != tt.expected {
75 t.Errorf("expected %v, got %v for tool %s action %s with allowlist %v",
76 tt.expected, allowed, tt.toolName, tt.action, tt.allowedTools)
77 }
78 })
79 }
80}
81
82func TestPermissionService_SkipMode(t *testing.T) {
83 service := NewPermissionService("/tmp", true, []string{})
84
85 result, err := service.Request(t.Context(), CreatePermissionRequest{
86 SessionID: "test-session",
87 ToolName: "bash",
88 Action: "execute",
89 Description: "test command",
90 Path: "/tmp",
91 })
92 if err != nil {
93 t.Errorf("unexpected error: %v", err)
94 }
95 if !result {
96 t.Error("expected permission to be granted in skip mode")
97 }
98}
99
100func TestPermissionService_SequentialProperties(t *testing.T) {
101 t.Run("Sequential permission requests with persistent grants", func(t *testing.T) {
102 service := NewPermissionService("/tmp", false, []string{})
103
104 req1 := CreatePermissionRequest{
105 SessionID: "session1",
106 ToolName: "file_tool",
107 Description: "Read file",
108 Action: "read",
109 Params: map[string]string{"file": "test.txt"},
110 Path: "/tmp/test.txt",
111 }
112
113 var result1 bool
114 var wg sync.WaitGroup
115 wg.Add(1)
116
117 events := service.Subscribe(t.Context())
118
119 go func() {
120 defer wg.Done()
121 result1, _ = service.Request(t.Context(), req1)
122 }()
123
124 var permissionReq PermissionRequest
125 event := <-events
126
127 permissionReq = event.Payload
128 service.GrantPersistent(permissionReq)
129
130 wg.Wait()
131 assert.True(t, result1, "First request should be granted")
132
133 // Second identical request should be automatically approved due to persistent permission
134 req2 := CreatePermissionRequest{
135 SessionID: "session1",
136 ToolName: "file_tool",
137 Description: "Read file again",
138 Action: "read",
139 Params: map[string]string{"file": "test.txt"},
140 Path: "/tmp/test.txt",
141 }
142 result2, err := service.Request(t.Context(), req2)
143 require.NoError(t, err)
144 assert.True(t, result2, "Second request should be auto-approved")
145 })
146 t.Run("Sequential requests with temporary grants", func(t *testing.T) {
147 service := NewPermissionService("/tmp", false, []string{})
148
149 req := CreatePermissionRequest{
150 SessionID: "session2",
151 ToolName: "file_tool",
152 Description: "Write file",
153 Action: "write",
154 Params: map[string]string{"file": "test.txt"},
155 Path: "/tmp/test.txt",
156 }
157
158 events := service.Subscribe(t.Context())
159 var result1 bool
160 var wg sync.WaitGroup
161
162 wg.Go(func() {
163 result1, _ = service.Request(t.Context(), req)
164 })
165
166 var permissionReq PermissionRequest
167 event := <-events
168 permissionReq = event.Payload
169
170 service.Grant(permissionReq)
171 wg.Wait()
172 assert.True(t, result1, "First request should be granted")
173
174 var result2 bool
175
176 wg.Go(func() {
177 result2, _ = service.Request(t.Context(), req)
178 })
179
180 event = <-events
181 permissionReq = event.Payload
182 service.Deny(permissionReq)
183 wg.Wait()
184 assert.False(t, result2, "Second request should be denied")
185 })
186 t.Run("Concurrent requests with different outcomes", func(t *testing.T) {
187 service := NewPermissionService("/tmp", false, []string{})
188
189 events := service.Subscribe(t.Context())
190
191 var wg sync.WaitGroup
192 results := make([]bool, 3)
193
194 requests := []CreatePermissionRequest{
195 {
196 SessionID: "concurrent1",
197 ToolName: "tool1",
198 Action: "action1",
199 Path: "/tmp/file1.txt",
200 Description: "First concurrent request",
201 },
202 {
203 SessionID: "concurrent2",
204 ToolName: "tool2",
205 Action: "action2",
206 Path: "/tmp/file2.txt",
207 Description: "Second concurrent request",
208 },
209 {
210 SessionID: "concurrent3",
211 ToolName: "tool3",
212 Action: "action3",
213 Path: "/tmp/file3.txt",
214 Description: "Third concurrent request",
215 },
216 }
217
218 for i, req := range requests {
219 wg.Add(1)
220 go func(index int, request CreatePermissionRequest) {
221 defer wg.Done()
222 result, _ := service.Request(t.Context(), request)
223 results[index] = result
224 }(i, req)
225 }
226
227 for range 3 {
228 event := <-events
229 switch event.Payload.ToolName {
230 case "tool1":
231 service.Grant(event.Payload)
232 case "tool2":
233 service.GrantPersistent(event.Payload)
234 case "tool3":
235 service.Deny(event.Payload)
236 }
237 }
238 wg.Wait()
239 grantedCount := 0
240 for _, result := range results {
241 if result {
242 grantedCount++
243 }
244 }
245
246 assert.Equal(t, 2, grantedCount, "Should have 2 granted and 1 denied")
247 secondReq := requests[1]
248 secondReq.Description = "Repeat of second request"
249 result, err := service.Request(t.Context(), secondReq)
250 require.NoError(t, err)
251 assert.True(t, result, "Repeated request should be auto-approved due to persistent permission")
252 })
253}