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}