v0.6.0 d5cc8fe

token_test.go RawHistoryFolder

1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package domain_test
4
5import (
6	"testing"
7	"time"
8
9	"github.com/stretchr/testify/assert"
10	"github.com/stretchr/testify/require"
11
12	"bnema.dev/ermite/internal/domain"
13)
14
15func TestScopes(t *testing.T) {
16	all := domain.AllScopes()
17	assert.Len(t, all, 9)
18	for _, s := range all {
19		got, err := domain.ParseScope(s.String())
20		require.NoError(t, err)
21		assert.Equal(t, s, got)
22	}
23	for _, in := range []string{"", "repo", "repo:READ", "admin", "repo:read "} {
24		_, err := domain.ParseScope(in)
25		assert.ErrorIs(t, err, domain.ErrInvalidScope, in)
26	}
27}
28
29func TestHashTokenSecret(t *testing.T) {
30	h := domain.HashTokenSecret("ermite_abc")
31	assert.Equal(t, h, domain.HashTokenSecret("ermite_abc"))
32	assert.NotEqual(t, h, domain.HashTokenSecret("ermite_abd"))
33	assert.Len(t, h.String(), 64)
34	// SHA-256("") known value.
35	assert.Equal(t, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", domain.HashTokenSecret("").String())
36}
37
38func TestTokenExpired(t *testing.T) {
39	now := time.Date(2026, 10, 10, 8, 0, 0, 0, time.UTC)
40	assert.False(t, domain.Token{}.Expired(now), "no expiry never expires")
41	assert.False(t, domain.Token{ExpiresAt: now.Add(time.Second)}.Expired(now))
42	assert.True(t, domain.Token{ExpiresAt: now}.Expired(now), "expires at the instant")
43	assert.True(t, domain.Token{ExpiresAt: now.Add(-time.Second)}.Expired(now))
44}
45
46func TestPrincipalScopes(t *testing.T) {
47	a, b := mustName(t, "a"), mustName(t, "b")
48	tok := domain.Token{ID: "t1", Scopes: []domain.Scope{domain.ScopeRepoRead, domain.ScopeRepoWrite}, Repos: []domain.RepoName{a}}
49	p := domain.TokenPrincipal(tok)
50
51	assert.Equal(t, "token", p.Label())
52	assert.Equal(t, "t1", p.TokenID())
53	assert.False(t, p.IsRoot())
54	assert.False(t, p.IsAnonymous())
55	assert.True(t, p.HasScope(domain.ScopeRepoWrite))
56	assert.False(t, p.HasScope(domain.ScopeTokenAdmin))
57	assert.True(t, p.Allows(domain.ScopeRepoWrite, a))
58	assert.False(t, p.Allows(domain.ScopeRepoWrite, b), "outside the repository list")
59	assert.False(t, p.Allows(domain.ScopeRepoAdmin, a), "scope not held")
60
61	// Without a list, every repository.
62	all := domain.TokenPrincipal(domain.Token{Scopes: []domain.Scope{domain.ScopeRepoRead}})
63	assert.True(t, all.Allows(domain.ScopeRepoRead, a))
64	assert.True(t, all.Allows(domain.ScopeRepoRead, b))
65
66	// The principal owns copies.
67	tok.Scopes[0] = domain.ScopeTokenAdmin
68	assert.True(t, p.HasScope(domain.ScopeRepoRead))
69	p.Scopes()[0] = domain.ScopeTokenAdmin
70	assert.True(t, p.HasScope(domain.ScopeRepoRead))
71
72	assert.True(t, domain.Root().Allows(domain.ScopeTokenAdmin, a))
73	assert.False(t, domain.Anonymous().HasScope(domain.ScopeRepoRead))
74	assert.False(t, domain.Anonymous().Allows(domain.ScopeRepoRead, a))
75	assert.Equal(t, "root", domain.Root().Label())
76	assert.Equal(t, "anonymous", domain.Anonymous().Label())
77}
78
79func mustName(t *testing.T, s string) domain.RepoName {
80	t.Helper()
81	n, err := domain.ParseRepoName(s)
82	require.NoError(t, err)
83	return n
84}
85
86func TestOwnerPrincipal(t *testing.T) {
87	o := domain.Owner()
88	assert.Equal(t, "owner", o.Label())
89	assert.True(t, o.IsOwner())
90	assert.False(t, domain.Root().IsOwner())
91	assert.True(t, o.IsRoot(), "the owner has the rights of root")
92	assert.True(t, o.HasScope(domain.ScopeMRMerge))
93	assert.True(t, o.Allows(domain.ScopeRepoRead, mustName(t, "any")))
94	assert.False(t, o.IsAnonymous())
95	assert.Empty(t, o.TokenID())
96}
97
98func TestHasOwnerRights(t *testing.T) {
99	a := mustName(t, "a")
100	all := domain.AllScopes()
101	assert.True(t, domain.Root().HasOwnerRights())
102	assert.True(t, domain.Owner().HasOwnerRights())
103	assert.False(t, domain.Anonymous().HasOwnerRights())
104	assert.True(t, domain.TokenPrincipal(domain.Token{Scopes: all}).HasOwnerRights())
105	assert.False(t, domain.TokenPrincipal(domain.Token{Scopes: all[1:]}).HasOwnerRights(), "a scope short")
106	assert.False(t, domain.TokenPrincipal(domain.Token{Scopes: all, Repos: []domain.RepoName{a}}).HasOwnerRights(), "limited to a repository")
107}

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 3

Tags 43

↑↓ moveenter openesc close