e2e/org_test.go

v1.38.0
gitbay/e2e/org_test.go history · blame · raw

186 lines · 8195 bytes

  1package e2e
  2
  3import (
  4	"os"
  5	"path/filepath"
  6	"strings"
  7	"testing"
  8)
  9
 10func TestOrganizations(t *testing.T) {
 11	t.Parallel()
 12	inst := startInstance(t)
 13	aliceKey := inst.newKey(t, "alice")
 14	bobKey := inst.newKey(t, "bob")
 15	eveKey := inst.newKey(t, "eve")
 16	inst.admin(t, "admin", "user", "create", "alice",
 17		"--key", aliceKey+".pub", "--email", "alice@example.test", "--verified")
 18	inst.admin(t, "admin", "user", "create", "bob", "--key", bobKey+".pub")
 19	inst.admin(t, "admin", "user", "create", "eve", "--key", eveKey+".pub")
 20
 21	// Alice creates an org; the namespace is shared with users.
 22	if _, errOut, code := inst.ssh(t, aliceKey, "", "org", "create", "krz"); code != 0 {
 23		t.Fatalf("org create: %s", errOut)
 24	}
 25	if _, _, code := inst.ssh(t, aliceKey, "", "org", "create", "bob"); code == 0 {
 26		t.Fatal("org created with a user's name")
 27	}
 28	if out := inst.admin(t, "admin", "user", "create", "krz2", "--key", inst.newKey(t, "krz2")+".pub"); out == "" {
 29		t.Fatal("control user create failed")
 30	}
 31	// A user cannot claim an org's name either.
 32	cmd := inst.forgedAdminErr(t, "admin", "user", "create", "krz")
 33	if !strings.Contains(cmd, "taken") {
 34		t.Fatalf("user with org name: %s", cmd)
 35	}
 36
 37	// Only org admins create repos under the org.
 38	if _, errOut, code := inst.ssh(t, bobKey, "", "repo", "create", "krz/lib"); code != 4 {
 39		t.Fatalf("non-member org repo create: %d %s", code, errOut)
 40	}
 41	if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "krz/lib", "--private"); code != 0 {
 42		t.Fatalf("org repo create: %s", errOut)
 43	}
 44
 45	// Membership-derived access: bob (member) gets write, eve (outsider)
 46	// sees nothing on the private repo.
 47	if _, errOut, code := inst.ssh(t, aliceKey, "", "org", "members", "add", "krz", "bob"); code != 0 {
 48		t.Fatalf("members add: %s", errOut)
 49	}
 50	work := t.TempDir()
 51	bobEnv := inst.gitEnv(bobKey)
 52	mustGit(t, work, bobEnv, "clone", inst.sshURL("krz/lib"), "w")
 53	dir := filepath.Join(work, "w")
 54	os.WriteFile(filepath.Join(dir, "f.txt"), []byte("org work\n"), 0o644)
 55	mustGit(t, dir, bobEnv, "checkout", "-q", "-b", "main")
 56	mustGit(t, dir, bobEnv, "add", ".")
 57	mustGit(t, dir, bobEnv, "commit", "-q", "-m", "bob pushes to org repo")
 58	mustGit(t, dir, bobEnv, "push", "-q", "origin", "main")
 59
 60	if out, code := gitRun(t, t.TempDir(), inst.gitEnv(eveKey), "clone", inst.sshURL("krz/lib")); code == 0 || !strings.Contains(out, "repository not found") {
 61		t.Fatalf("outsider on private org repo: %d\n%s", code, out)
 62	}
 63
 64	// Members are not repo admins: bob cannot change settings or grant
 65	// access; an org admin can.
 66	if _, _, code := inst.ssh(t, bobKey, "", "repo", "settings", "protect", "krz/lib", "main"); code != 4 {
 67		t.Fatal("member changed org repo settings")
 68	}
 69	if _, _, code := inst.ssh(t, bobKey, "", "org", "members", "add", "krz", "eve"); code != 4 {
 70		t.Fatal("member managed org membership")
 71	}
 72	if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "settings", "protect", "krz/lib", "main"); code != 0 {
 73		t.Fatalf("org admin protect: %s", errOut)
 74	}
 75
 76	// Explicit per-repo grants still work alongside membership: eve gets
 77	// read on the private org repo.
 78	if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "access", "grant", "krz/lib", "eve", "read"); code != 0 {
 79		t.Fatalf("grant: %s", errOut)
 80	}
 81	mustGit(t, t.TempDir(), inst.gitEnv(eveKey), "clone", inst.sshURL("krz/lib"))
 82
 83	// Org repos list for members; org shows in org list.
 84	out, _, _ := inst.ssh(t, bobKey, "", "repo", "list")
 85	if !strings.Contains(out, "krz/lib") {
 86		t.Fatalf("member repo list missing org repo:\n%s", out)
 87	}
 88	out, _, _ = inst.ssh(t, bobKey, "", "org", "list")
 89	if !strings.Contains(out, "krz\tmember") {
 90		t.Fatalf("org list: %s", out)
 91	}
 92
 93	// Promotion works; the last admin is protected.
 94	if _, errOut, code := inst.ssh(t, aliceKey, "", "org", "members", "add", "krz", "bob", "--role", "admin"); code != 0 {
 95		t.Fatalf("promote: %s", errOut)
 96	}
 97	if _, errOut, code := inst.ssh(t, bobKey, "", "org", "members", "remove", "krz", "alice"); code != 0 {
 98		t.Fatalf("bob (now admin) removing alice: %s", errOut)
 99	}
100	_, errOut, code := inst.ssh(t, bobKey, "", "org", "members", "remove", "krz", "bob")
101	if code != 1 || !strings.Contains(errOut, "at least one admin") {
102		t.Fatalf("last admin removal: %d %s", code, errOut)
103	}
104
105	// Org deletion refuses while repos exist, then succeeds.
106	_, errOut, code = inst.ssh(t, bobKey, "", "org", "delete", "krz", "--yes")
107	if code != 1 || !strings.Contains(errOut, "still owns") {
108		t.Fatalf("delete with repos: %d %s", code, errOut)
109	}
110	if _, errOut, code = inst.ssh(t, bobKey, "", "repo", "delete", "krz/lib", "--yes"); code != 0 {
111		t.Fatalf("org repo delete: %s", errOut)
112	}
113	if _, errOut, code = inst.ssh(t, bobKey, "", "org", "delete", "krz", "--yes"); code != 0 {
114		t.Fatalf("org delete: %s", errOut)
115	}
116
117	// Transfer: org repo moves to a user; old path gone, new path clones,
118	// target collisions and non-admin transfers are refused.
119	if _, _, code = inst.ssh(t, aliceKey, "", "org", "create", "movers"); code != 0 {
120		t.Fatal("org movers failed")
121	}
122	if _, _, code = inst.ssh(t, aliceKey, "", "repo", "create", "movers/box"); code != 0 {
123		t.Fatal("movers/box failed")
124	}
125	tw := t.TempDir()
126	mustGit(t, tw, inst.gitEnv(aliceKey), "clone", inst.sshURL("movers/box"), "b1")
127	if _, errOut, code = inst.ssh(t, bobKey, "", "repo", "transfer", "movers/box", "bob"); code != 4 {
128		t.Fatalf("non-admin transfer: %d %s", code, errOut)
129	}
130	if _, errOut, code = inst.ssh(t, aliceKey, "", "repo", "transfer", "movers/box", "alice"); code != 0 {
131		t.Fatalf("transfer: %s", errOut)
132	}
133	mustGit(t, tw, inst.gitEnv(aliceKey), "clone", inst.sshURL("alice/box"), "b2")
134	if out, code := gitRun(t, t.TempDir(), inst.gitEnv(aliceKey), "clone", inst.sshURL("movers/box")); code == 0 {
135		t.Fatalf("old transfer path still clones:\n%s", out)
136	}
137	if _, _, code = inst.ssh(t, aliceKey, "", "repo", "create", "movers/box"); code != 0 {
138		t.Fatal("recreate movers/box failed")
139	}
140	if _, errOut, code = inst.ssh(t, aliceKey, "", "repo", "transfer", "movers/box", "alice"); code == 0 || !strings.Contains(errOut, "already") {
141		t.Fatalf("collision transfer: %d %s", code, errOut)
142	}
143
144	// Rename: clone works at the new path, old path is gone, collisions
145	// with users and existing orgs are refused.
146	if _, _, code = inst.ssh(t, aliceKey, "", "org", "create", "oldname"); code != 0 {
147		t.Fatal("org create oldname failed")
148	}
149	if _, _, code = inst.ssh(t, aliceKey, "", "repo", "create", "oldname/thing"); code != 0 {
150		t.Fatal("repo under oldname failed")
151	}
152	rnWork := t.TempDir()
153	mustGit(t, rnWork, inst.gitEnv(aliceKey), "clone", inst.sshURL("oldname/thing"), "w1")
154	if _, errOut, code = inst.ssh(t, aliceKey, "", "org", "rename", "oldname", "bob"); code != 1 || !strings.Contains(errOut, "taken") {
155		t.Fatalf("rename onto user name: %d %s", code, errOut)
156	}
157	if _, errOut, code = inst.ssh(t, aliceKey, "", "org", "rename", "oldname", "newname"); code != 0 {
158		t.Fatalf("rename: %s", errOut)
159	}
160	mustGit(t, rnWork, inst.gitEnv(aliceKey), "clone", inst.sshURL("newname/thing"), "w2")
161	if out, code := gitRun(t, t.TempDir(), inst.gitEnv(aliceKey), "clone", inst.sshURL("oldname/thing")); code == 0 {
162		t.Fatalf("old org path still clones:\n%s", out)
163	}
164	if out, _, _ := inst.ssh(t, aliceKey, "", "repo", "list"); !strings.Contains(out, "newname/thing") {
165		t.Fatalf("renamed org missing from repo list:\n%s", out)
166	}
167
168	// Public org repos appear on the anonymous web index.
169	if _, _, code = inst.ssh(t, aliceKey, "", "org", "create", "puborg"); code != 0 {
170		t.Fatal("org create failed")
171	}
172	if _, _, code = inst.ssh(t, aliceKey, "", "repo", "create", "puborg/site"); code != 0 {
173		t.Fatal("org public repo failed")
174	}
175	status, body := inst.get(t, "/explore")
176	if status != 200 || !strings.Contains(body, "puborg/site") {
177		t.Fatalf("org repo missing from explore: %d", status)
178	}
179	if status, _ := inst.get(t, "/puborg/site"); status != 200 {
180		t.Fatalf("org repo page: %d", status)
181	}
182	status, body = inst.get(t, "/puborg/-/repositories")
183	if status != 200 || !strings.Contains(body, "org") || !strings.Contains(body, "alice") || !strings.Contains(body, ">site<") {
184		t.Fatalf("org owner page: %d\n%s", status, body)
185	}
186}