internal/control/token_test.go
118 lines · 3728 bytes
1package control
2
3import (
4 "bytes"
5 "slices"
6 "strings"
7 "testing"
8 "time"
9
10 "gitbay.org/gitbay/internal/protocol"
11 "gitbay.org/gitbay/internal/store"
12)
13
14// The minting commands, pinned: adding one to the list, or dropping
15// one, is a decision this test makes someone take.
16func TestMintingCommandsMarked(t *testing.T) {
17 want := []string{
18 "admin email verify", "admin invite", "admin user create", "email verify",
19 "keys add", "repo deploy-key add", "repo runner add", "token create", "web login",
20 }
21 var got []string
22 for _, cmd := range Commands() {
23 if cmd.MintsCredential {
24 got = append(got, joinPath(cmd.Path))
25 }
26 }
27 slices.Sort(got)
28 if !slices.Equal(got, want) {
29 t.Fatalf("MintsCredential on %q, want %q", got, want)
30 }
31}
32
33// Dispatch refuses before the command runs, so no arguments are needed.
34func TestExpiringCredentialCannotMint(t *testing.T) {
35 exp := time.Now().Add(time.Hour)
36 for _, cmd := range Commands() {
37 if !cmd.MintsCredential {
38 continue
39 }
40 var out, errOut bytes.Buffer
41 c := &Ctx{User: store.User{ID: 1, Username: "root", IsAdmin: true}, Scope: "full", Expires: &exp, Stdout: &out, Stderr: &errOut}
42 if code := Dispatch(c, cmd.Path); code != protocol.ExitDenied || !strings.Contains(errOut.String(), "expires") {
43 t.Errorf("%s: exit %d %q, want %d and the reason", joinPath(cmd.Path), code, errOut.String(), protocol.ExitDenied)
44 }
45 }
46}
47
48// #286: at a terminal, a token expiring in the future must not render
49// through relAge, which clamps a future time to zero and prints
50// "expires just now".
51func TestTokenListFutureExpiryAtTerminal(t *testing.T) {
52 st, _, uid := newQueueTestRepo(t)
53 exp := time.Now().Add(90 * 24 * time.Hour)
54 if err := st.CreateAPIToken(uid, "laptop", "h-laptop", "read", &exp, 0); err != nil {
55 t.Fatal(err)
56 }
57 c, errOut := pruneCtx(st, t.TempDir(), store.User{ID: uid, Username: "alice"})
58 c.Term = Term{Cols: 80}
59 var out bytes.Buffer
60 c.Stdout = &out
61 if code := Dispatch(c, []string{"token", "list"}); code != protocol.ExitOK {
62 t.Fatalf("exit %d: %s", code, errOut)
63 }
64 if strings.Contains(out.String(), "just now") {
65 t.Fatalf("token list at a terminal printed \"just now\" for a future expiry:\n%s", out.String())
66 }
67 if !strings.Contains(out.String(), exp.UTC().Format("2006-01-02")) {
68 t.Fatalf("token list:\n%s", out.String())
69 }
70}
71
72func TestTokenCreateDefaultsToReadAndRecordsCreator(t *testing.T) {
73 st, _, uid := newQueueTestRepo(t)
74 if err := st.CreateAPIToken(uid, "parent", "h-parent", "full", nil, 0); err != nil {
75 t.Fatal(err)
76 }
77 _, parent, err := st.APITokenUser("h-parent")
78 if err != nil {
79 t.Fatal(err)
80 }
81 c, errOut := pruneCtx(st, t.TempDir(), store.User{ID: uid, Username: "alice"})
82 c.Cfg.Limits.WriteRate = -1
83 c.TokenID = parent.ID
84 if code := Dispatch(c, []string{"token", "create", "--name", "child"}); code != protocol.ExitOK {
85 t.Fatalf("exit %d: %s", code, errOut)
86 }
87 toks, err := st.ListAPITokens(uid)
88 if err != nil {
89 t.Fatal(err)
90 }
91 var found bool
92 for _, tk := range toks {
93 if tk.Name != "child" {
94 continue
95 }
96 found = true
97 if tk.Scope != "read" || tk.CreatedBy != "parent" {
98 t.Fatalf("child: %+v", tk)
99 }
100 }
101 if !found {
102 t.Fatal(`no token named "child"`)
103 }
104}
105
106func TestTokenCreateRefusesNonPositiveTTL(t *testing.T) {
107 st, _, uid := newQueueTestRepo(t)
108 for _, ttl := range []string{"0s", "-1h"} {
109 c, _ := pruneCtx(st, t.TempDir(), store.User{ID: uid, Username: "alice"})
110 c.Cfg.Limits.WriteRate = -1
111 if code := Dispatch(c, []string{"token", "create", "--name", "x", "--ttl", ttl}); code != protocol.ExitUsage {
112 t.Errorf("--ttl %s: exit %d", ttl, code)
113 }
114 }
115 if toks, err := st.ListAPITokens(uid); err != nil || len(toks) != 0 {
116 t.Fatalf("tokens: %+v %v", toks, err)
117 }
118}