main 5041353

permission_test.go RawHistoryFolder

1package dialog
2
3import (
4	"context"
5	"testing"
6
7	"github.com/bnema/dumber/internal/application/port"
8	"github.com/bnema/dumber/internal/domain/entity"
9	dialogmocks "github.com/bnema/dumber/internal/ui/dialog/mocks"
10	"github.com/stretchr/testify/assert"
11	"github.com/stretchr/testify/mock"
12)
13
14type permissionPopupShowCall struct {
15	heading string
16	body    string
17}
18
19func TestPermissionDialog_QueuesRequestsWhilePopupVisible(t *testing.T) {
20	popup := dialogmocks.NewMockPermissionPopup(t)
21	var showCalls []permissionPopupShowCall
22	var callback func(allowed, persistent bool)
23	popup.EXPECT().
24		Show(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
25		Run(func(_ context.Context, heading string, body string, _ bool, cb func(allowed, persistent bool)) {
26			showCalls = append(showCalls, permissionPopupShowCall{
27				heading: heading,
28				body:    body,
29			})
30			callback = cb
31		}).
32		Twice()
33
34	respond := func(allowed, persistent bool) {
35		if callback == nil {
36			return
37		}
38		cb := callback
39		callback = nil
40		cb(allowed, persistent)
41	}
42
43	d := &PermissionDialog{popup: popup}
44
45	var firstResult port.PermissionDialogResult
46	firstCalled := false
47	d.ShowPermissionDialog(
48		context.Background(),
49		"https://example.com",
50		[]entity.PermissionType{entity.PermissionTypeMicrophone},
51		nil,
52		func(result port.PermissionDialogResult) {
53			firstCalled = true
54			firstResult = result
55		},
56	)
57
58	secondCalled := false
59	var secondResult port.PermissionDialogResult
60	d.ShowPermissionDialog(
61		context.Background(),
62		"https://example.com",
63		[]entity.PermissionType{entity.PermissionTypeCamera},
64		nil,
65		func(result port.PermissionDialogResult) {
66			secondCalled = true
67			secondResult = result
68		},
69	)
70
71	if assert.Len(t, showCalls, 1) {
72		assert.Equal(t, "Allow Microphone Access?", showCalls[0].heading)
73	}
74	assert.False(t, firstCalled)
75	assert.False(t, secondCalled)
76
77	respond(true, false)
78	assert.True(t, firstCalled)
79	assert.Equal(t, port.PermissionDialogResult{Allowed: true, Persistent: false}, firstResult)
80
81	if assert.Len(t, showCalls, 2) {
82		assert.Equal(t, "Allow Camera Access?", showCalls[1].heading)
83	}
84	assert.False(t, secondCalled)
85
86	respond(false, true)
87	assert.True(t, secondCalled)
88	assert.Equal(t, port.PermissionDialogResult{Allowed: false, Persistent: true}, secondResult)
89}
90
91func TestPermissionDialog_NoPopup_DeniesRequest(t *testing.T) {
92	d := &PermissionDialog{popup: nil}
93
94	called := false
95	result := port.PermissionDialogResult{}
96	d.ShowPermissionDialog(
97		context.Background(),
98		"https://example.com",
99		[]entity.PermissionType{entity.PermissionTypeMicrophone},
100		nil,
101		func(r port.PermissionDialogResult) {
102			called = true
103			result = r
104		},
105	)
106
107	assert.True(t, called)
108	assert.Equal(t, port.PermissionDialogResult{Allowed: false, Persistent: false}, result)
109}
110
111func TestPermissionDialog_BuildHeadingAndBody_DisplayCombinations(t *testing.T) {
112	d := &PermissionDialog{}
113	origin := "https://meet.example.com"
114
115	tests := []struct {
116		name           string
117		permTypes      []entity.PermissionType
118		expectHeading  string
119		expectedAction string
120	}{
121		{
122			name:           "display only",
123			permTypes:      []entity.PermissionType{entity.PermissionTypeDisplay},
124			expectHeading:  "Allow Screen Sharing?",
125			expectedAction: "share your screen",
126		},
127		{
128			name:           "microphone and display",
129			permTypes:      []entity.PermissionType{entity.PermissionTypeMicrophone, entity.PermissionTypeDisplay},
130			expectHeading:  "Allow Microphone and Screen Sharing?",
131			expectedAction: "access your microphone and share your screen",
132		},
133		{
134			name:           "camera and display",
135			permTypes:      []entity.PermissionType{entity.PermissionTypeCamera, entity.PermissionTypeDisplay},
136			expectHeading:  "Allow Camera and Screen Sharing?",
137			expectedAction: "access your camera and share your screen",
138		},
139		{
140			name:           "all three",
141			permTypes:      []entity.PermissionType{entity.PermissionTypeMicrophone, entity.PermissionTypeCamera, entity.PermissionTypeDisplay},
142			expectHeading:  "Allow Microphone, Camera, and Screen Sharing?",
143			expectedAction: "access your microphone and camera, and share your screen",
144		},
145		{
146			name:           "notification only",
147			permTypes:      []entity.PermissionType{entity.PermissionTypeNotification},
148			expectHeading:  "Allow Notifications?",
149			expectedAction: "show notifications",
150		},
151		{
152			name:           "geolocation only",
153			permTypes:      []entity.PermissionType{entity.PermissionTypeGeolocation},
154			expectHeading:  "Allow Location Access?",
155			expectedAction: "access your location",
156		},
157		{
158			name:           "website data access only",
159			permTypes:      []entity.PermissionType{entity.PermissionTypeWebsiteDataAccess},
160			expectHeading:  "Allow Third-Party Data Access?",
161			expectedAction: "access its stored data while you browse this site",
162		},
163	}
164
165	for _, tt := range tests {
166		t.Run(tt.name, func(t *testing.T) {
167			assert.Equal(t, tt.expectHeading, d.buildHeading(tt.permTypes))
168			assert.Equal(t, origin+" wants to "+tt.expectedAction+".", d.buildBody(origin, tt.permTypes, nil))
169		})
170	}
171
172	// Test website_data_access with populated metadata (Epiphany-style rich message)
173	t.Run("website data access with both domains", func(t *testing.T) {
174		meta := entity.PermissionMetadata{
175			entity.PermissionMetadataKeyRequestingDomain: "accounts.google.com",
176			entity.PermissionMetadataKeyCurrentDomain:    "shop.example.com",
177		}
178		body := d.buildBody(origin, []entity.PermissionType{entity.PermissionTypeWebsiteDataAccess}, meta)
179		assert.Equal(t, origin+" wants to allow accounts.google.com to access its data (including cookies) while you browse shop.example.com.", body)
180	})
181}
182
183func TestPermissionDialog_BuildBody_UnmediatedDisplay(t *testing.T) {
184	d := &PermissionDialog{}
185	origin := "https://meet.example.com"
186	meta := entity.PermissionMetadata{
187		entity.PermissionMetadataKeyUnmediatedCapture: entity.PermissionMetadataValueTrue,
188	}
189
190	assert.Equal(t, origin+" wants to share your entire screen.",
191		d.buildBody(origin, []entity.PermissionType{entity.PermissionTypeDisplay}, meta))
192	assert.Equal(t, origin+" wants to access your camera and share your entire screen.",
193		d.buildBody(origin, []entity.PermissionType{entity.PermissionTypeCamera, entity.PermissionTypeDisplay}, meta))
194	// Mediated display (WebKit portal) keeps the generic wording.
195	assert.Equal(t, origin+" wants to share your screen.",
196		d.buildBody(origin, []entity.PermissionType{entity.PermissionTypeDisplay}, nil))
197}
198
199func TestPermissionDialog_BuildBody_UnmediatedDisplayWithSystemAudio(t *testing.T) {
200	d := &PermissionDialog{}
201	origin := "https://meet.example.com"
202	meta := entity.PermissionMetadata{
203		entity.PermissionMetadataKeyUnmediatedCapture: entity.PermissionMetadataValueTrue,
204		entity.PermissionMetadataKeyUnmediatedAudio:   entity.PermissionMetadataValueTrue,
205	}
206
207	assert.Equal(t, origin+" wants to share your entire screen and system audio.",
208		d.buildBody(origin, []entity.PermissionType{entity.PermissionTypeDisplay}, meta))
209}
210
211func TestAnyPersistable(t *testing.T) {
212	tests := []struct {
213		name  string
214		types []entity.PermissionType
215		want  bool
216	}{
217		{"display only", []entity.PermissionType{entity.PermissionTypeDisplay}, false},
218		{"microphone", []entity.PermissionType{entity.PermissionTypeMicrophone}, true},
219		{"microphone and display", []entity.PermissionType{entity.PermissionTypeMicrophone, entity.PermissionTypeDisplay}, true},
220		{"geolocation", []entity.PermissionType{entity.PermissionTypeGeolocation}, true},
221		{"none", nil, false},
222	}
223	for _, tt := range tests {
224		t.Run(tt.name, func(t *testing.T) {
225			assert.Equal(t, tt.want, anyPersistable(tt.types))
226		})
227	}
228}
229
230func TestPermissionDialog_PassesPersistableToPopup(t *testing.T) {
231	tests := []struct {
232		name  string
233		types []entity.PermissionType
234		want  bool
235	}{
236		{"display only hides always buttons", []entity.PermissionType{entity.PermissionTypeDisplay}, false},
237		{"mixed keeps always buttons", []entity.PermissionType{entity.PermissionTypeMicrophone, entity.PermissionTypeDisplay}, true},
238	}
239	for _, tt := range tests {
240		t.Run(tt.name, func(t *testing.T) {
241			popup := dialogmocks.NewMockPermissionPopup(t)
242			var got *bool
243			popup.EXPECT().
244				Show(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
245				Run(func(_ context.Context, _ string, _ string, persistable bool, _ func(allowed, persistent bool)) {
246					got = &persistable
247				}).Once()
248
249			d := &PermissionDialog{popup: popup}
250			d.ShowPermissionDialog(context.Background(), "https://meet.example.com", tt.types, nil, func(port.PermissionDialogResult) {})
251
252			if assert.NotNil(t, got) {
253				assert.Equal(t, tt.want, *got)
254			}
255		})
256	}
257}
258
259func TestPermissionDialog_ExternalLaunchConfirmHidesQueryAndIsNotPersistable(t *testing.T) {
260	popup := dialogmocks.NewMockPermissionPopup(t)
261	var body string
262	var persistable bool
263	var cb func(allowed, persistent bool)
264	popup.EXPECT().
265		Show(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
266		Run(func(_ context.Context, _ string, b string, p bool, c func(allowed, persistent bool)) {
267			body, persistable, cb = b, p, c
268		}).Once()
269
270	d := &PermissionDialog{popup: popup}
271	var allowed bool
272	d.ShowExternalLaunchConfirm(context.Background(), "claude.ai", "claude://login/google-auth?code=secret", func(a bool) { allowed = a })
273
274	assert.Contains(t, body, "claude.ai wants to open claude://login")
275	assert.NotContains(t, body, "secret")
276	assert.False(t, persistable)
277
278	cb(true, false)
279	assert.True(t, allowed)
280}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close