e2e/quota_test.go
108 lines · 4641 bytes
1package e2e
2
3import (
4 "crypto/rand"
5 "fmt"
6 "os"
7 "path/filepath"
8 "strings"
9 "testing"
10 "time"
11)
12
13// Per-account caps on repositories and storage, with the admin override,
14// and expiry of accounts that never verified.
15func TestQuotasAndPendingExpiry(t *testing.T) {
16 t.Setenv("GITBAY_REAP_TICK", "500ms")
17 smtp := startFakeSMTP(t)
18 inst := startInstanceWith(t, fmt.Sprintf(
19 "[registration]\nmode = \"open\"\npending_expiry = \"2s\"\n[mail]\nsmtp_host = %q\nfrom = \"noreply@gitbay.test\"\n"+
20 "[limits]\nmax_repos_per_user = 2\nmax_bytes_per_user = 300000\n", smtp.addr))
21 rootKey := inst.newKey(t, "root")
22 aliceKey := inst.newKey(t, "alice")
23 inst.admin(t, "admin", "user", "create", "root", "--key", rootKey+".pub", "--admin")
24 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
25
26 // Two repositories fit; the third is refused with the numbers.
27 for _, r := range []string{"alice/one", "alice/two"} {
28 if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", r); code != 0 {
29 t.Fatalf("create %s: %s", r, errOut)
30 }
31 }
32 if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/three"); code != 4 || !strings.Contains(errOut, "2 of the 2 repositories") {
33 t.Fatalf("third repo: exit %d %s", code, errOut)
34 }
35 if _, _, code := inst.ssh(t, aliceKey, "", "repo", "fork", "alice/one", "--name", "onefork"); code != 4 {
36 t.Fatal("fork slipped past the cap")
37 }
38 // An org is not capped.
39 if _, _, code := inst.ssh(t, aliceKey, "", "org", "create", "acme"); code != 0 {
40 t.Fatal("org create failed")
41 }
42 if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "acme/lib"); code != 0 {
43 t.Fatalf("org repo: %s", errOut)
44 }
45 // The admin raises the cap for this account; the third fits.
46 if out, _, code := inst.ssh(t, rootKey, "", "admin", "user", "limits", "alice", "--repos", "3"); code != 0 || !strings.Contains(out, "repos 2 of 3") {
47 t.Fatalf("limits: exit %d %s", code, out)
48 }
49 if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/three"); code != 0 {
50 t.Fatalf("third repo after raise: %s", errOut)
51 }
52 if out, _, _ := inst.ssh(t, rootKey, "", "admin", "user", "show", "alice", "--json"); !strings.Contains(out, `"repo_limit":3`) || !strings.Contains(out, `"byte_limit":300000`) {
53 t.Fatalf("show lacks limits:\n%s", out)
54 }
55 if out, _, _ := inst.ssh(t, rootKey, "", "admin", "user", "limits", "alice", "--repos", "default"); !strings.Contains(out, "of 2") {
56 t.Fatalf("limits back to default:\n%s", out)
57 }
58
59 // Storage: a push past what the account has left is refused.
60 work := t.TempDir()
61 env := inst.gitEnv(aliceKey)
62 mustGit(t, work, env, "clone", inst.sshURL("alice/one"), "w")
63 dir := filepath.Join(work, "w")
64 os.WriteFile(filepath.Join(dir, "small.txt"), []byte("ok\n"), 0o644)
65 mustGit(t, dir, env, "checkout", "-q", "-b", "main")
66 mustGit(t, dir, env, "add", ".")
67 mustGit(t, dir, env, "commit", "-q", "-m", "small")
68 mustGit(t, dir, env, "push", "-q", "origin", "main")
69 big := make([]byte, 400_000)
70 rand.Read(big)
71 os.WriteFile(filepath.Join(dir, "big.bin"), big, 0o644)
72 mustGit(t, dir, env, "add", ".")
73 mustGit(t, dir, env, "commit", "-q", "-m", "big")
74 if out, code := gitRun(t, dir, env, "push", "origin", "main"); code == 0 || !strings.Contains(out, "max") {
75 t.Fatalf("push past the storage cap accepted: exit %d\n%s", code, out)
76 }
77 if _, _, code := inst.ssh(t, rootKey, "", "admin", "user", "limits", "alice", "--bytes", "0"); code != 0 {
78 t.Fatal("lift byte cap failed")
79 }
80 mustGit(t, dir, env, "push", "-q", "origin", "main")
81
82 // An account that registers and never verifies is removed after
83 // pending_expiry; the name is free again.
84 newKey := inst.newKey(t, "dana")
85 if _, errOut, code := inst.ssh(t, newKey, "", "register", "--username", "dana", "--email", "dana@example.test"); code != 0 {
86 t.Fatalf("register: %s", errOut)
87 }
88 if out, _, _ := inst.ssh(t, rootKey, "", "admin", "user", "list", "--state", "pending"); !strings.HasPrefix(out, "dana\t") {
89 t.Fatalf("dana not pending:\n%s", out)
90 }
91 deadline := time.Now().Add(15 * time.Second)
92 for {
93 out, _, _ := inst.ssh(t, rootKey, "", "admin", "user", "list", "--state", "pending")
94 if strings.TrimSpace(out) == "" {
95 break
96 }
97 if time.Now().After(deadline) {
98 t.Fatalf("pending account never expired:\n%s", out)
99 }
100 time.Sleep(300 * time.Millisecond)
101 }
102 if _, _, code := inst.ssh(t, newKey, "", "whoami"); code == 0 {
103 t.Fatal("expired account still authenticates")
104 }
105 if out := inst.admin(t, "admin", "audit", "--action", "pending.expired"); !strings.Contains(out, `"user":"dana"`) {
106 t.Fatalf("expiry not audited:\n%s", out)
107 }
108}