v2.7.0 fac7705

auth_test.go RawHistoryFolder

1package domain
2
3import (
4	"testing"
5
6	"github.com/stretchr/testify/assert"
7	"github.com/stretchr/testify/require"
8)
9
10func TestParseScope(t *testing.T) {
11	tests := []struct {
12		name        string
13		input       string
14		wantScope   *Scope
15		wantErr     bool
16		errContains string
17	}{
18		// Valid cases
19		{
20			name:  "simple repository scope",
21			input: "repository:myrepo:pull",
22			wantScope: &Scope{
23				Type:    "repository",
24				Name:    "myrepo",
25				Actions: []string{"pull"},
26			},
27		},
28		{
29			name:  "multiple actions",
30			input: "repository:myrepo:push,pull",
31			wantScope: &Scope{
32				Type:    "repository",
33				Name:    "myrepo",
34				Actions: []string{"push", "pull"},
35			},
36		},
37		{
38			name:  "wildcard repository",
39			input: "repository:*:pull",
40			wantScope: &Scope{
41				Type:    "repository",
42				Name:    "*",
43				Actions: []string{"pull"},
44			},
45		},
46		{
47			name:  "org wildcard",
48			input: "repository:myorg/*:push,pull",
49			wantScope: &Scope{
50				Type:    "repository",
51				Name:    "myorg/*",
52				Actions: []string{"push", "pull"},
53			},
54		},
55		{
56			name:  "wildcard action",
57			input: "repository:myrepo:*",
58			wantScope: &Scope{
59				Type:    "repository",
60				Name:    "myrepo",
61				Actions: []string{"*"},
62			},
63		},
64		{
65			name:  "nested repository name",
66			input: "repository:myorg/team/app:pull",
67			wantScope: &Scope{
68				Type:    "repository",
69				Name:    "myorg/team/app",
70				Actions: []string{"pull"},
71			},
72		},
73		{
74			name:  "actions with whitespace trimmed",
75			input: "repository:myrepo:push, pull",
76			wantScope: &Scope{
77				Type:    "repository",
78				Name:    "myrepo",
79				Actions: []string{"push", "pull"},
80			},
81		},
82		{
83			name:  "registry scope type",
84			input: "registry:catalog:*",
85			wantScope: &Scope{
86				Type:    "registry",
87				Name:    "catalog",
88				Actions: []string{"*"},
89			},
90		},
91
92		// Invalid cases
93		{
94			name:        "empty string",
95			input:       "",
96			wantErr:     true,
97			errContains: "invalid scope format",
98		},
99		{
100			name:        "missing actions",
101			input:       "repository:myrepo",
102			wantErr:     true,
103			errContains: "invalid scope format",
104		},
105		{
106			name:        "missing name and actions",
107			input:       "repository",
108			wantErr:     true,
109			errContains: "invalid scope format",
110		},
111		{
112			name:  "only type and name",
113			input: "repository:myrepo:",
114			wantScope: &Scope{
115				Type:    "repository",
116				Name:    "myrepo",
117				Actions: []string{""},
118			},
119		},
120	}
121
122	for _, tt := range tests {
123		t.Run(tt.name, func(t *testing.T) {
124			scope, err := ParseScope(tt.input)
125
126			if tt.wantErr {
127				require.Error(t, err)
128				if tt.errContains != "" {
129					assert.Contains(t, err.Error(), tt.errContains)
130				}
131				return
132			}
133
134			require.NoError(t, err)
135			assert.Equal(t, tt.wantScope.Type, scope.Type)
136			assert.Equal(t, tt.wantScope.Name, scope.Name)
137			assert.Equal(t, tt.wantScope.Actions, scope.Actions)
138		})
139	}
140}
141
142func TestScope_CanAccess(t *testing.T) {
143	tests := []struct {
144		name     string
145		scope    *Scope
146		repoName string
147		action   string
148		want     bool
149	}{
150		// Exact match cases
151		{
152			name:     "exact repo match with pull",
153			scope:    &Scope{Type: "repository", Name: "myrepo", Actions: []string{"pull"}},
154			repoName: "myrepo",
155			action:   "pull",
156			want:     true,
157		},
158		{
159			name:     "exact repo match with push",
160			scope:    &Scope{Type: "repository", Name: "myrepo", Actions: []string{"push"}},
161			repoName: "myrepo",
162			action:   "push",
163			want:     true,
164		},
165		{
166			name:     "exact repo match wrong action",
167			scope:    &Scope{Type: "repository", Name: "myrepo", Actions: []string{"pull"}},
168			repoName: "myrepo",
169			action:   "push",
170			want:     false,
171		},
172		{
173			name:     "wrong repo name",
174			scope:    &Scope{Type: "repository", Name: "myrepo", Actions: []string{"pull"}},
175			repoName: "otherrepo",
176			action:   "pull",
177			want:     false,
178		},
179
180		// Wildcard repository cases
181		{
182			name:     "wildcard repo matches any",
183			scope:    &Scope{Type: "repository", Name: "*", Actions: []string{"pull"}},
184			repoName: "anyrepo",
185			action:   "pull",
186			want:     true,
187		},
188		{
189			name:     "wildcard repo matches nested",
190			scope:    &Scope{Type: "repository", Name: "*", Actions: []string{"pull"}},
191			repoName: "myorg/myapp",
192			action:   "pull",
193			want:     true,
194		},
195
196		// Org-level wildcard cases
197		{
198			name:     "org wildcard matches repo in org",
199			scope:    &Scope{Type: "repository", Name: "myorg/*", Actions: []string{"pull"}},
200			repoName: "myorg/myapp",
201			action:   "pull",
202			want:     true,
203		},
204		{
205			name:     "org wildcard matches nested repo in org",
206			scope:    &Scope{Type: "repository", Name: "myorg/*", Actions: []string{"pull"}},
207			repoName: "myorg/team/myapp",
208			action:   "pull",
209			want:     true,
210		},
211		{
212			name:     "org wildcard does not match other org",
213			scope:    &Scope{Type: "repository", Name: "myorg/*", Actions: []string{"pull"}},
214			repoName: "otherorg/myapp",
215			action:   "pull",
216			want:     false,
217		},
218		{
219			name:     "org wildcard does not match repo without slash",
220			scope:    &Scope{Type: "repository", Name: "myorg/*", Actions: []string{"pull"}},
221			repoName: "myorg",
222			action:   "pull",
223			want:     false,
224		},
225
226		// Wildcard action cases
227		{
228			name:     "wildcard action grants pull",
229			scope:    &Scope{Type: "repository", Name: "myrepo", Actions: []string{"*"}},
230			repoName: "myrepo",
231			action:   "pull",
232			want:     true,
233		},
234		{
235			name:     "wildcard action grants push",
236			scope:    &Scope{Type: "repository", Name: "myrepo", Actions: []string{"*"}},
237			repoName: "myrepo",
238			action:   "push",
239			want:     true,
240		},
241
242		// Multiple actions
243		{
244			name:     "multiple actions includes requested",
245			scope:    &Scope{Type: "repository", Name: "myrepo", Actions: []string{"push", "pull"}},
246			repoName: "myrepo",
247			action:   "pull",
248			want:     true,
249		},
250		{
251			name:     "multiple actions includes push",
252			scope:    &Scope{Type: "repository", Name: "myrepo", Actions: []string{"push", "pull"}},
253			repoName: "myrepo",
254			action:   "push",
255			want:     true,
256		},
257
258		// Non-repository type
259		{
260			name:     "registry type does not grant repo access",
261			scope:    &Scope{Type: "registry", Name: "catalog", Actions: []string{"*"}},
262			repoName: "myrepo",
263			action:   "pull",
264			want:     false,
265		},
266
267		// Edge cases
268		{
269			name:     "empty actions",
270			scope:    &Scope{Type: "repository", Name: "myrepo", Actions: []string{}},
271			repoName: "myrepo",
272			action:   "pull",
273			want:     false,
274		},
275		{
276			name:     "empty repo name in scope",
277			scope:    &Scope{Type: "repository", Name: "", Actions: []string{"pull"}},
278			repoName: "myrepo",
279			action:   "pull",
280			want:     false,
281		},
282	}
283
284	for _, tt := range tests {
285		t.Run(tt.name, func(t *testing.T) {
286			got := tt.scope.CanAccess(tt.repoName, tt.action)
287			assert.Equal(t, tt.want, got)
288		})
289	}
290}
291
292func TestScope_String(t *testing.T) {
293	tests := []struct {
294		name  string
295		scope *Scope
296		want  string
297	}{
298		{
299			name:  "simple scope",
300			scope: &Scope{Type: "repository", Name: "myrepo", Actions: []string{"pull"}},
301			want:  "repository:myrepo:pull",
302		},
303		{
304			name:  "multiple actions",
305			scope: &Scope{Type: "repository", Name: "myrepo", Actions: []string{"push", "pull"}},
306			want:  "repository:myrepo:push,pull",
307		},
308		{
309			name:  "wildcard",
310			scope: &Scope{Type: "repository", Name: "*", Actions: []string{"*"}},
311			want:  "repository:*:*",
312		},
313		{
314			name:  "org wildcard",
315			scope: &Scope{Type: "repository", Name: "myorg/*", Actions: []string{"push", "pull"}},
316			want:  "repository:myorg/*:push,pull",
317		},
318	}
319
320	for _, tt := range tests {
321		t.Run(tt.name, func(t *testing.T) {
322			got := tt.scope.String()
323			assert.Equal(t, tt.want, got)
324		})
325	}
326}
327
328func TestParseScope_RoundTrip(t *testing.T) {
329	// Test that parsing and stringifying produces the same result
330	inputs := []string{
331		"repository:myrepo:pull",
332		"repository:myrepo:push,pull",
333		"repository:*:*",
334		"repository:myorg/*:push,pull",
335		"repository:myorg/team/app:pull",
336	}
337
338	for _, input := range inputs {
339		t.Run(input, func(t *testing.T) {
340			scope, err := ParseScope(input)
341			require.NoError(t, err)
342
343			output := scope.String()
344			assert.Equal(t, input, output)
345		})
346	}
347}

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 7

Tags 106

↑↓ moveenter openesc close