1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package domain_test
4
5import (
6 "strings"
7 "testing"
8 "time"
9
10 "github.com/stretchr/testify/assert"
11
12 "bnema.dev/ermite/internal/domain"
13)
14
15func TestIsRepoToken(t *testing.T) {
16 t.Parallel()
17 ok := domain.RepoTokenPrefix + strings.Repeat("aB3-_", 8) + "xyz"
18 assert.True(t, domain.IsRepoToken(ok))
19 for _, bad := range []string{
20 "", domain.RepoTokenPrefix, ok + "x", ok[:len(ok)-1], strings.Replace(ok, "ermitegh_", "ermitejob_", 1),
21 domain.RepoTokenPrefix + strings.Repeat("a", 42) + "=", domain.RepoTokenPrefix + strings.Repeat("a", 42) + " ",
22 domain.TokenSecretPrefix + strings.Repeat("a", 43),
23 } {
24 assert.False(t, domain.IsRepoToken(bad), bad)
25 }
26 assert.False(t, domain.IsJobToken(ok), "the two kinds of secret are never mistaken")
27}
28
29func TestRepoTokenPrincipalReadsOneRepositoryAndNothingElse(t *testing.T) {
30 t.Parallel()
31 repo, _ := domain.ParseRepoName("proj")
32 other, _ := domain.ParseRepoName("other")
33 exp := time.Date(2026, 10, 13, 12, 0, 0, 0, time.UTC)
34 p := domain.RepoTokenPrincipal(domain.CIRepoToken{ID: "ab12", Repo: repo, ExpiresAt: exp})
35
36 assert.True(t, p.Allows(domain.ScopeRepoRead, repo))
37 assert.False(t, p.Allows(domain.ScopeRepoRead, other))
38 for _, s := range domain.AllScopes() {
39 if s != domain.ScopeRepoRead {
40 assert.False(t, p.Allows(s, repo), s)
41 }
42 }
43 assert.False(t, p.IsRoot())
44 assert.False(t, p.HasOwnerRights())
45 assert.False(t, p.IsAnonymous())
46 assert.Equal(t, exp, p.ExpiresAt())
47 assert.True(t, domain.IsRepoTokenID(p.TokenID()))
48 assert.False(t, domain.IsRepoTokenID("0123456789abcdef"), "a stored token id is 16 hex characters")
49
50 tok := domain.CIRepoToken{ExpiresAt: exp}
51 assert.False(t, tok.Expired(exp.Add(-time.Second)))
52 assert.True(t, tok.Expired(exp), "the expiry instant is already over")
53}
54
55func TestArchivePrincipalHoldsNoScopeAndOneGrant(t *testing.T) {
56 t.Parallel()
57 repo, _ := domain.ParseRepoName("action")
58 sha := strings.Repeat("a", 40)
59 exp := time.Date(2026, 10, 13, 12, 0, 0, 0, time.UTC)
60 p := domain.RepoTokenPrincipal(domain.CIRepoToken{ID: "ab", Repo: repo, Archive: true, Sha: sha, ExpiresAt: exp})
61
62 gr, gs, ok := p.ArchiveGrant()
63 assert.True(t, ok)
64 assert.Equal(t, repo, gr)
65 assert.Equal(t, sha, gs)
66 for _, s := range domain.AllScopes() {
67 assert.False(t, p.HasScope(s), s)
68 assert.False(t, p.Allows(s, repo), s)
69 }
70 assert.False(t, p.IsRoot())
71 assert.False(t, p.IsAnonymous(), "it is authenticated, with nothing to read")
72 assert.False(t, p.HasOwnerRights())
73 assert.Equal(t, exp, p.ExpiresAt())
74 assert.True(t, domain.IsRepoTokenID(p.TokenID()))
75 assert.Equal(t, "archive", p.Label())
76
77 own := domain.RepoTokenPrincipal(domain.CIRepoToken{ID: "cd", Repo: repo, ExpiresAt: exp})
78 _, _, ok = own.ArchiveGrant()
79 assert.False(t, ok, "the job's own token has no grant, it has the scope")
80 for _, other := range []domain.Principal{domain.Anonymous(), domain.Root(), domain.Owner()} {
81 _, _, ok = other.ArchiveGrant()
82 assert.False(t, ok)
83 }
84}
85
86func TestPusherOf(t *testing.T) {
87 t.Parallel()
88 repo, _ := domain.ParseRepoName("proj")
89 assert.Equal(t, domain.CIPusher{Kind: domain.CIPusherRoot}, domain.PusherOf(domain.Root()))
90 assert.Equal(t, domain.CIPusher{Kind: domain.CIPusherOwner}, domain.PusherOf(domain.Owner()))
91 assert.Equal(t, domain.CIPusher{Kind: domain.CIPusherToken, TokenID: "abc"},
92 domain.PusherOf(domain.TokenPrincipal(domain.Token{ID: "abc", Scopes: []domain.Scope{domain.ScopeRepoWrite}})))
93 assert.Equal(t, domain.CIPusher{}, domain.PusherOf(domain.Anonymous()))
94 assert.Equal(t, domain.CIPusher{}, domain.PusherOf(domain.RepoTokenPrincipal(domain.CIRepoToken{ID: "x", Repo: repo})), "a job token never pushes")
95}
96
97func TestOwnerMatches(t *testing.T) {
98 t.Parallel()
99 assert.True(t, domain.OwnerMatches("acme", "acme"))
100 assert.True(t, domain.OwnerMatches("acme", "ACME"))
101 assert.True(t, domain.OwnerMatches("acme", "Acme"))
102 assert.False(t, domain.OwnerMatches("acme", "acme2"))
103 assert.False(t, domain.OwnerMatches("acme", ""))
104 assert.False(t, domain.OwnerMatches("", ""), "no owner configured matches nothing")
105 assert.False(t, domain.OwnerMatches("", "acme"))
106}