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