e2e/quota_test.go

v1.43.1
gitbay/e2e/quota_test.go history · blame · raw

136 lines · 6384 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 and per-org caps on repositories and storage, the cap on
 14// orgs an account creates, the admin overrides, and expiry of accounts
 15// that never verified.
 16func TestQuotasAndPendingExpiry(t *testing.T) {
 17	t.Setenv("GITBAY_REAP_TICK", "500ms")
 18	smtp := startFakeSMTP(t)
 19	inst := startInstanceWith(t, fmt.Sprintf(
 20		"[registration]\nmode = \"open\"\npending_expiry = \"2s\"\n[mail]\nsmtp_host = %q\nfrom = \"noreply@gitbay.test\"\n"+
 21			"[limits]\nmax_repos_per_user = 2\nmax_bytes_per_user = 300000\n"+
 22			"max_orgs_per_user = 1\nmax_repos_per_org = 1\n", smtp.addr))
 23	rootKey := inst.newKey(t, "root")
 24	aliceKey := inst.newKey(t, "alice")
 25	inst.admin(t, "admin", "user", "create", "root", "--key", rootKey+".pub", "--admin")
 26	inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
 27
 28	// Two repositories fit; the third is refused with the numbers.
 29	for _, r := range []string{"alice/one", "alice/two"} {
 30		if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", r); code != 0 {
 31			t.Fatalf("create %s: %s", r, errOut)
 32		}
 33	}
 34	if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/three"); code != 4 || !strings.Contains(errOut, "2 of the 2 repositories") {
 35		t.Fatalf("third repo: exit %d %s", code, errOut)
 36	}
 37	if _, _, code := inst.ssh(t, aliceKey, "", "repo", "fork", "alice/one", "--name", "onefork"); code != 4 {
 38		t.Fatal("fork slipped past the cap")
 39	}
 40	// One org fits; the second is refused.
 41	if _, _, code := inst.ssh(t, aliceKey, "", "org", "create", "acme"); code != 0 {
 42		t.Fatal("org create failed")
 43	}
 44	if _, errOut, code := inst.ssh(t, aliceKey, "", "org", "create", "acme2"); code != 4 || !strings.Contains(errOut, "1 of the 1 organizations") {
 45		t.Fatalf("second org: exit %d %s", code, errOut)
 46	}
 47	// The org has its own repository cap, which the admin raises per org.
 48	if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "acme/lib"); code != 0 {
 49		t.Fatalf("org repo: %s", errOut)
 50	}
 51	if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "acme/lib2"); code != 4 || !strings.Contains(errOut, "1 of the 1 repositories") {
 52		t.Fatalf("second org repo: exit %d %s", code, errOut)
 53	}
 54	if out, _, code := inst.ssh(t, rootKey, "", "admin", "org", "limits", "acme", "--repos", "2"); code != 0 || !strings.Contains(out, "repos 1 of 2") {
 55		t.Fatalf("org limits: exit %d %s", code, out)
 56	}
 57	if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "acme/lib2"); code != 0 {
 58		t.Fatalf("second org repo after raise: %s", errOut)
 59	}
 60	if out, _, code := inst.ssh(t, rootKey, "", "admin", "user", "limits", "alice", "--orgs", "2"); code != 0 || !strings.Contains(out, "orgs 1 of 2") {
 61		t.Fatalf("user org limit: exit %d %s", code, out)
 62	}
 63	if _, errOut, code := inst.ssh(t, aliceKey, "", "org", "create", "acme2"); code != 0 {
 64		t.Fatalf("second org after raise: %s", errOut)
 65	}
 66	// The admin raises the cap for this account; the third fits.
 67	if out, _, code := inst.ssh(t, rootKey, "", "admin", "user", "limits", "alice", "--repos", "3"); code != 0 || !strings.Contains(out, "repos 2 of 3") {
 68		t.Fatalf("limits: exit %d %s", code, out)
 69	}
 70	if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/three"); code != 0 {
 71		t.Fatalf("third repo after raise: %s", errOut)
 72	}
 73	if out, _, _ := inst.ssh(t, rootKey, "", "admin", "user", "show", "alice", "--json"); !strings.Contains(out, `"repo_limit":3`) || !strings.Contains(out, `"byte_limit":300000`) {
 74		t.Fatalf("show lacks limits:\n%s", out)
 75	}
 76	if out, _, _ := inst.ssh(t, rootKey, "", "admin", "user", "limits", "alice", "--repos", "default"); !strings.Contains(out, "of 2") {
 77		t.Fatalf("limits back to default:\n%s", out)
 78	}
 79	// A transfer in counts as a create for the receiving owner.
 80	if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "transfer", "acme/lib2", "alice"); code != 4 || !strings.Contains(errOut, "3 of the 2 repositories") {
 81		t.Fatalf("transfer past the cap: exit %d %s", code, errOut)
 82	}
 83	if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "transfer", "alice/three", "acme2"); code != 0 {
 84		t.Fatalf("transfer into an org with room: %s", errOut)
 85	}
 86
 87	// Storage: a push past what the account has left is refused.
 88	work := t.TempDir()
 89	env := inst.gitEnv(aliceKey)
 90	mustGit(t, work, env, "clone", inst.sshURL("alice/one"), "w")
 91	dir := filepath.Join(work, "w")
 92	os.WriteFile(filepath.Join(dir, "small.txt"), []byte("ok\n"), 0o644)
 93	mustGit(t, dir, env, "checkout", "-q", "-b", "main")
 94	mustGit(t, dir, env, "add", ".")
 95	mustGit(t, dir, env, "commit", "-q", "-m", "small")
 96	mustGit(t, dir, env, "push", "-q", "origin", "main")
 97	big := make([]byte, 400_000)
 98	rand.Read(big)
 99	os.WriteFile(filepath.Join(dir, "big.bin"), big, 0o644)
100	mustGit(t, dir, env, "add", ".")
101	mustGit(t, dir, env, "commit", "-q", "-m", "big")
102	if out, code := gitRun(t, dir, env, "push", "origin", "main"); code == 0 || !strings.Contains(out, "max") {
103		t.Fatalf("push past the storage cap accepted: exit %d\n%s", code, out)
104	}
105	if _, _, code := inst.ssh(t, rootKey, "", "admin", "user", "limits", "alice", "--bytes", "0"); code != 0 {
106		t.Fatal("lift byte cap failed")
107	}
108	mustGit(t, dir, env, "push", "-q", "origin", "main")
109
110	// An account that registers and never verifies is removed after
111	// pending_expiry; the name is free again.
112	newKey := inst.newKey(t, "dana")
113	if _, errOut, code := inst.ssh(t, newKey, "", "register", "--username", "dana", "--email", "dana@example.test"); code != 0 {
114		t.Fatalf("register: %s", errOut)
115	}
116	if out, _, _ := inst.ssh(t, rootKey, "", "admin", "user", "list", "--state", "pending"); !strings.HasPrefix(out, "dana\t") {
117		t.Fatalf("dana not pending:\n%s", out)
118	}
119	deadline := time.Now().Add(15 * time.Second)
120	for {
121		out, _, _ := inst.ssh(t, rootKey, "", "admin", "user", "list", "--state", "pending")
122		if strings.TrimSpace(out) == "" {
123			break
124		}
125		if time.Now().After(deadline) {
126			t.Fatalf("pending account never expired:\n%s", out)
127		}
128		time.Sleep(300 * time.Millisecond)
129	}
130	if _, _, code := inst.ssh(t, newKey, "", "whoami"); code == 0 {
131		t.Fatal("expired account still authenticates")
132	}
133	if out := inst.admin(t, "admin", "audit", "--action", "pending.expired"); !strings.Contains(out, `"user":"dana"`) {
134		t.Fatalf("expiry not audited:\n%s", out)
135	}
136}